Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Base / System / IO / Packaging / CompoundFile / StorageInfo.cs / 1 / StorageInfo.cs
//------------------------------------------------------------------------------ // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // Class for manipulating storages in the container file. // // History: // 05/13/2002: RogerCh: Initial creation. // 06/25/2002: RogerCh: Data space support. // 07/31/2002: RogerCh: Make obvious that we are using security suppressed interfaces. // 05/19/2003: RogerCh: Port to WCP tree. // 05/28/2003: RogerCh: Added long name support // 06/20/2003: RogerCh: GetStreams() and GetSubStorages() // 08/11/2003: LGolding: Fix Bug 864168 (some of BruceMac's bug fixes were lost // in port to WCP tree). // //----------------------------------------------------------------------------- using System; using System.Collections; using System.ComponentModel; // For EditorBrowsable attribute using System.Diagnostics; // For Assert using System.Security; using System.Security.Permissions; using System.IO; using System.Globalization; // CultureInfo.InvariantCulture using System.Windows; // SR.Get(SRID.[exception message]) using MS.Internal.IO.Packaging.CompoundFile; using CU = MS.Internal.IO.Packaging.CompoundFile.ContainerUtilities; using MS.Internal; // for Invariant & CriticalExceptions using System.Runtime.InteropServices; // COMException #pragma warning disable 1634, 1691 // suppressing PreSharp warnings namespace System.IO.Packaging { ////// This class holds the core information for a StorageInfo object. /// internal class StorageInfoCore { internal StorageInfoCore( string nameStorage ) : this( nameStorage, null ) {;} internal StorageInfoCore( string nameStorage, IStorage storage ) { storageName = nameStorage; safeIStorage = storage; validEnumerators = new Hashtable(); // Storage and Stream names: we preserve casing, but do case-insensitive comparison (Native CompoundFile API behavior) elementInfoCores = new Hashtable(CU.StringCaseInsensitiveComparer); } ////// The compound-file friendly version name. /// internal string storageName; ////// A reference to "this" storage. This value is non-null only when /// 1) The storage exists /// 2) We had reason to open it. /// The value may be null even when the storage exists because we may not /// have need to go and open it. /// internal IStorage safeIStorage; ////// We keep track of the enumerator objects that we've handed out. /// If anything is changed in this storage, we go and invalidate all of /// them and clear the list. /// /// In theory a simple ListDictionary class is more efficient than using /// a Hashtable class. But since we're bringing in the code for the /// HashTable class anyway, the savings of using ListDictionary went away. /// So even with a maximum of three elements, we use a Hashtable. /// internal Hashtable validEnumerators; ////// This hash table holds the standing "core" objects for its child /// elements. Each element may be a StorageInfoCore or a /// StreamInfoCore. /// internal Hashtable elementInfoCores; } ////// Class for manipulating storages in the container file /// public class StorageInfo { /***********************************************************************/ // Instance values ////// Each storage holds a reference to its parent, this way even if the /// client app releases the reference it'll be kept in the reference graph /// to avoid getting prematurely garbage-collected. /// /// The only time this is allowed to be null is when this storage is the /// root storage. /// StorageInfo parentStorage; ////// Each storage holds a reference to the container root. This value will /// be equal to null for the container root. /// StorageRoot rootStorage; ////// There is one StorageInfoCore object per underlying IStorage. If /// multiple StorageInfo objects are created that point to the same /// underlying storage, they share the same StorageInfoCore object. /// These are maintained by the parent storage for all its child /// storages, with the exception of the root StorageInfo which keeps /// its own instance in StorageRoot. /// internal StorageInfoCore core; // Instance name for the compression transform private static readonly string sc_compressionTransformName = "CompressionTransform"; //Dataspace label definitions for compression and encryption combinations while creating a stream private static readonly string sc_dataspaceLabelNoEncryptionNormalCompression = "NoEncryptionNormalCompression"; private static readonly string sc_dataspaceLabelRMEncryptionNormalCompression = "RMEncryptionNormalCompression"; ////// We can have three valid enumerator types. /// private enum EnumeratorTypes { Everything, OnlyStorages, OnlyStreams } /***********************************************************************/ // Constructors ////// A constructor for building the root storage. /// This should only happen for, well, the root storage! /// internal StorageInfo( IStorage safeIStorage ) { core = new StorageInfoCore( null, safeIStorage ); } ////// Given a parent and a path under it, step through each of the path /// elements and create an intermediate StorageInfo at each step because /// a StorageInfo is only meaningful relative to its immediate parent - /// multi-step relations can't be represented. /// private void BuildStorageInfoRelativeToStorage( StorageInfo parent, string fileName ) { parentStorage = parent; core = parent.CoreForChildStorage( fileName ); rootStorage = parent.Root; } ////// Constructor for a StorageInfo given a parent StorageInfo and a name /// /// Reference to the parent storage /// filename for the new StorageInfo internal StorageInfo( StorageInfo parent, string fileName ) { CU.CheckAgainstNull( parent, "parent" ); CU.CheckAgainstNull( fileName, "fileName" ); BuildStorageInfoRelativeToStorage( parent, fileName ); } ////// Respond to a request from a child StorageInfo object to give /// it a StorageInfoCore object for the given name. /// StorageInfoCore CoreForChildStorage( string storageNname ) { CheckDisposedStatus(); object childElement = core.elementInfoCores[ storageNname ]; if( null != childElement && null == childElement as StorageInfoCore ) { // Name is already in use, but not as a StorageInfo throw new InvalidOperationException( SR.Get(SRID.NameAlreadyInUse, storageNname )); } else if( null == childElement ) { // No such element with the name exist - create one. // childElement = new StorageInfoCore( storageNname); core.elementInfoCores[ storageNname ] = childElement; } Debug.Assert( null != childElement as StorageInfoCore, "We should have already checked to make sure childElement is either StorageInfoCore, created as one, or thrown an exception if neither is possible"); return childElement as StorageInfoCore; } internal StreamInfoCore CoreForChildStream( string streamName ) { CheckDisposedStatus(); object childElement = core.elementInfoCores[ streamName ]; if( null != childElement && null == childElement as StreamInfoCore ) { // Name is already in use, but not as a StreamInfo throw new InvalidOperationException( SR.Get(SRID.NameAlreadyInUse, streamName )); } else if( null == childElement ) { // No such element with the name exist - create one. // // Check to see if there is a data space mapping on this guy DataSpaceManager manager = Root.GetDataSpaceManager(); if( null != manager ) { // Have data space manager - retrieve data space label for // the child stream. The data space manager will return // null if there isn't a data space associated with this // stream. childElement = new StreamInfoCore( streamName, manager.DataSpaceOf( new CompoundFileStreamReference( FullNameInternal, streamName ) ) ); } else { // Data space manager not yet initialized - correct behavior // is that nothing transformed should be required at this // point but in case of incorrect behavior we can not possibly // recover. User gets un-transformed data instead. childElement = new StreamInfoCore( streamName, null, null ); } core.elementInfoCores[ streamName ] = childElement; } Debug.Assert( null != childElement as StreamInfoCore, "We should have already checked to make sure childElement is either StreamInfoCore, created as one, or thrown an exception if neither is possible"); return childElement as StreamInfoCore; } /***********************************************************************/ // public Properties ////// The name for this storage /// public string Name { get { CheckDisposedStatus(); return core.storageName; } } /***********************************************************************/ // public Methods ////// Creates "this" stream /// /// Name of stream /// CompressionOptiont /// EncryptionOption ///Reference to new stream public StreamInfo CreateStream( string name, CompressionOption compressionOption, EncryptionOption encryptionOption ) { CheckDisposedStatus(); //check the arguments if( null == name ) throw new ArgumentNullException("name"); // Stream names: we preserve casing, but do case-insensitive comparison (Native CompoundFile API behavior) if (((IEqualityComparer) CU.StringCaseInsensitiveComparer).Equals(name, EncryptedPackageEnvelope.PackageStreamName)) throw new ArgumentException(SR.Get(SRID.StreamNameNotValid,name)); //create a new streaminfo object StreamInfo streamInfo = new StreamInfo(this, name, compressionOption, encryptionOption); if (streamInfo.InternalExists()) { throw new IOException(SR.Get(SRID.StreamAlreadyExist)); } //Define the compression and encryption options in the dataspacemanager DataSpaceManager manager = Root.GetDataSpaceManager(); string dataSpaceLabel = null; if (manager != null) { //case : Compression option is set. Stream need to be compressed. Define compression transform. //At this time, we only treat CompressionOption - Normal and None. The rest are treated as Normal if (compressionOption != CompressionOption.NotCompressed) { //If it is not defined already, define it. if (!manager.TransformLabelIsDefined(sc_compressionTransformName)) manager.DefineTransform(CompressionTransform.ClassTransformIdentifier, sc_compressionTransformName); } //case : Encryption option is set. Stream need to be encrypted. Define encryption transform. if (encryptionOption == EncryptionOption.RightsManagement) { //If it not defined already, define it. if (!manager.TransformLabelIsDefined(EncryptedPackageEnvelope.EncryptionTransformName)) { //We really cannot define RM transform completely here because the transform initialization cannot be done here without publishlicense and cryptoprovider. //However it will always be defined because this method is accessed only through an EncryptedPackageEnvelope and RM transform is always defined in EncryptedPackageEnvelope.Create() throw new SystemException(SR.Get(SRID.RightsManagementEncryptionTransformNotFound)); } } //Now find the dataspace label that we need to define these transforms in. //CASE: When both CompressionOption and EncryptionOption are set if ( (compressionOption != CompressionOption.NotCompressed) && (encryptionOption == EncryptionOption.RightsManagement) ) { dataSpaceLabel = sc_dataspaceLabelRMEncryptionNormalCompression; if (!manager.DataSpaceIsDefined(dataSpaceLabel)) { string[] transformStack = new string[2]; //compress the data first. then encrypt it. This ordering will cause the content to be compressed, then encrypted, then written to the stream. transformStack[0] = EncryptedPackageEnvelope.EncryptionTransformName; transformStack[1] = sc_compressionTransformName; manager.DefineDataSpace(transformStack, dataSpaceLabel); } } //CASE : when CompressionOption alone is set else if ( (compressionOption != CompressionOption.NotCompressed) && (encryptionOption == EncryptionOption.None) ) { dataSpaceLabel = sc_dataspaceLabelNoEncryptionNormalCompression; if (!manager.DataSpaceIsDefined(dataSpaceLabel)) { string[] transformStack = new string[1]; transformStack[0] = sc_compressionTransformName; manager.DefineDataSpace(transformStack, dataSpaceLabel); } } //CASE : when EncryptionOption alone is set else if (encryptionOption == EncryptionOption.RightsManagement) { dataSpaceLabel = EncryptedPackageEnvelope.DataspaceLabelRMEncryptionNoCompression; if (!manager.DataSpaceIsDefined(dataSpaceLabel)) { string[] transformStack = new string[1]; transformStack[0] = EncryptedPackageEnvelope.EncryptionTransformName; manager.DefineDataSpace(transformStack, dataSpaceLabel); } } //All the other cases are not handled at this point. } //create the underlying stream if (null == dataSpaceLabel) streamInfo.Create(); //create the stream with default parameters else streamInfo.Create(dataSpaceLabel); //create the stream in the defined dataspace return streamInfo; } ////// Creates "this" stream /// /// Name of stream ///Reference to new stream public StreamInfo CreateStream( string name ) { //create the stream with out any compression or encryption options. return CreateStream(name,CompressionOption.NotCompressed,EncryptionOption.None); } ////// Returns the streaminfo by the passed name. /// /// Name of stream ///Reference to the stream public StreamInfo GetStreamInfo(string name) { CheckDisposedStatus(); //check the arguments if( null == name ) throw new ArgumentNullException("name"); StreamInfo streamInfo = new StreamInfo(this, name); if (streamInfo.InternalExists()) { return streamInfo; } else { throw new IOException(SR.Get(SRID.StreamNotExist)); } } ////// Check if the stream exists. /// /// Name of stream ///True if exists, False if not public bool StreamExists(string name) { CheckDisposedStatus(); bool streamExists = false; StreamInfo streamInfo = new StreamInfo(this, name); streamExists = streamInfo.InternalExists(); return streamExists; } ////// Deleted the stream with the passed name. /// /// Name of stream public void DeleteStream(string name) { CheckDisposedStatus(); //check the arguments if( null == name ) throw new ArgumentNullException("name"); StreamInfo streamInfo = new StreamInfo(this, name); if (streamInfo.InternalExists()) { streamInfo.Delete(); } } ////// Creates a storage using "this" one as parent /// /// Name of new storage ///Reference to new storage public StorageInfo CreateSubStorage( string name ) { CheckDisposedStatus(); //check the arguments if( null == name ) throw new ArgumentNullException("name"); return CreateStorage(name); } ////// Returns the storage by the passed name. /// /// Name of storage ///Reference to the storage public StorageInfo GetSubStorageInfo(string name) { //Find if this storage exists StorageInfo storageInfo = new StorageInfo(this, name); if (storageInfo.InternalExists(name)) { return storageInfo; } else { throw new IOException(SR.Get(SRID.StorageNotExist)); } } ////// Checks if a storage exists by the passed name. /// /// Name of storage ///Reference to new storage public bool SubStorageExists(string name) { StorageInfo storageInfo = new StorageInfo(this, name); return storageInfo.InternalExists(name); } ////// Deletes a storage recursively. /// /// Name of storage public void DeleteSubStorage(string name) { CheckDisposedStatus(); //check the arguments if( null == name ) throw new ArgumentNullException("name"); StorageInfo storageInfo = new StorageInfo(this, name); if (storageInfo.InternalExists(name)) { InvalidateEnumerators(); // Go ahead and delete "this" storage DestroyElement( name ); } //We will not throw exceptions if the storage does not exist. This is to be consistent with Package.DeletePart. } ////// Provides a snapshot picture of the streams currently within this storage /// object. The array that returns will not be updated with additions/ /// removals of streams, but the individual StreamInfo objects within will /// reflect the state of their respective streams. /// ////// This follows the precedent of DirectoryInfo.GetFiles() /// ////// Array of StreamInfo objects, each pointing to a stream within this /// storage. Empty (zero-length) array if there are no streams. /// public StreamInfo[] GetStreams() { // Make sure 'this' storage is alive and well. CheckDisposedStatus(); VerifyExists(); // Build an array of StreamInfo objects EnsureArrayForEnumeration(EnumeratorTypes.OnlyStreams); // Because we're handing out a snapshot, we can't simply hand out a // reference to the core.validEnumerators array. We need to make a // copy that has the references of the array. This way the array // we return will remain if we invalidate the arrays. // Fortunately ArrayList.ToArray makes a copy, perfect for our needs. ArrayList streamArray = (ArrayList)core.validEnumerators[EnumeratorTypes.OnlyStreams]; Invariant.Assert(streamArray != null); #pragma warning suppress 6506 // Invariant.Assert(streamArray != null) return (StreamInfo[])streamArray.ToArray(typeof(StreamInfo)); } ////// Provides a snapshot picture of the sub-storages currently within this storage /// object. The array that returns will not be updated with additions/ /// removals of storages, but the individual StorageInfo objects within will /// reflect the state of their respective sub-storages. /// ////// This follows the precedent of DirectoryInfo.GetDirectories() /// ////// Array of StorageInfo objects, each pointing to a sub-storage within this /// storage. Empty (zero-length) array if there are no sub-storages. /// public StorageInfo[] GetSubStorages() { // Make sure 'this' storage is alive and well. CheckDisposedStatus(); VerifyExists(); // See GetStreams counterpart for details. EnsureArrayForEnumeration(EnumeratorTypes.OnlyStorages); ArrayList storageArray = (ArrayList)core.validEnumerators[EnumeratorTypes.OnlyStorages]; Invariant.Assert(storageArray != null); #pragma warning suppress 6506 // Invariant.Assert(streamArray != null) return (StorageInfo[])storageArray.ToArray(typeof(StorageInfo)); } /***********************************************************************/ // Internal/Private functionality internal string FullNameInternal { get { CheckDisposedStatus(); return CU.ConvertStringArrayPathToBackSlashPath(BuildFullNameInternalFromParentNameInternal()); } } ////// Get a reference to the container instance /// internal StorageRoot Root { get { CheckDisposedStatus(); if( null == rootStorage ) return (StorageRoot)this; else return rootStorage; } } ////// Because it is valid to have a StorageInfo point to a storage that /// doesn't yet actually exist, use this to see if it does. /// internal bool Exists { get { CheckDisposedStatus(); return InternalExists(); } } ////// Creates "this" storage /// internal void Create() { CheckDisposedStatus(); if( null != parentStorage ) // Only root storage has null parentStorage { // Root storage always exists so we don't do any of this // if we're not the root. if( !parentStorage.Exists ) { // We need the parent to exist before we can exist. parentStorage.Create(); } if( !InternalExists() ) { // If we don't exist, then ask parent to create us. parentStorage.CreateStorage( core.storageName ); } //else if we already exist, we're done here. } } private StorageInfo CreateStorage(string name) { // Create new StorageInfo StorageInfo newSubStorage = new StorageInfo( this, name ); // Make it real if( !newSubStorage.InternalExists(name) ) { /* TBD if( !CU.IsValidCompoundFileName(name)) { throw new IOException( SR.Get(SRID.UnableToCreateStorage), new COMException( SR.Get(SRID.NamedAPIFailure, "IStorage.CreateStorage"), nativeCallErrorCode )); } */ // It doesn't already exist, please create. StorageInfoCore newStorage = core.elementInfoCores[ name ] as StorageInfoCore; Invariant.Assert( null != newStorage); int nativeCallErrorCode = core.safeIStorage.CreateStorage( name, (GetStat().grfMode & SafeNativeCompoundFileConstants.STGM_READWRITE_Bits) | SafeNativeCompoundFileConstants.STGM_SHARE_EXCLUSIVE, 0, 0, #pragma warning suppress 6506 // Invariant.Assert(null != newStorage) out newStorage.safeIStorage ); if( SafeNativeCompoundFileConstants.S_OK != nativeCallErrorCode ) { if( nativeCallErrorCode == SafeNativeCompoundFileConstants.STG_E_ACCESSDENIED ) { throw new UnauthorizedAccessException( SR.Get(SRID.CanNotCreateAccessDenied), new COMException( SR.Get(SRID.NamedAPIFailure, "IStorage.CreateStorage"), nativeCallErrorCode )); } else { throw new IOException( SR.Get(SRID.UnableToCreateStorage), new COMException( SR.Get(SRID.NamedAPIFailure, "IStorage.CreateStorage"), nativeCallErrorCode )); } } // Invalidate enumerators InvalidateEnumerators(); } else { throw new IOException(SR.Get(SRID.StorageAlreadyExist)); } // Return a reference return newSubStorage; } ////// Deletes a storage, recursively if specified. /// /// Whether to recursive delete all existing content /// Name of storage internal bool Delete( bool recursive , string name) { bool storageDeleted = false; CheckDisposedStatus(); if( null == parentStorage ) { // We are the root storage, you can't "delete" the root storage! throw new InvalidOperationException( SR.Get(SRID.CanNotDeleteRoot)); } if( InternalExists(name) ) { if( !recursive && !StorageIsEmpty()) { throw new IOException( SR.Get(SRID.CanNotDeleteNonEmptyStorage)); } InvalidateEnumerators(); // Go ahead and delete "this" storage parentStorage.DestroyElement( name ); storageDeleted = true; } //We will not throw exceptions if the storage does not exist. This is to be consistent with Package.DeletePart. return storageDeleted; } ////// When a substorage is getting deleted, its references in the dataspacemanager's transform definition are removed. /// This is called recursively because the DeleteSubStorage deletes all its children by default. /// internal void RemoveSubStorageEntryFromDataSpaceMap(StorageInfo storageInfo) { StorageInfo[] subStorages = storageInfo.GetSubStorages(); foreach(StorageInfo storage in subStorages) { //If this is a storage, call recursively till we encounter a stream. Then we can use that container (storage,stream) reference to remove from the // dataspace manager's data space map. RemoveSubStorageEntryFromDataSpaceMap(storage); //recursive call } //Now we have StorageInfo. Find if there is a stream underneath so a container can be used as reference in data space map of data spacemanager. StreamInfo[] streams = storageInfo.GetStreams(); DataSpaceManager manager = Root.GetDataSpaceManager(); foreach(StreamInfo stream in streams) { manager.RemoveContainerFromDataSpaceMap(new CompoundFileStreamReference( storageInfo.FullNameInternal, stream.Name )); } } ////// Destroys an element and removes the references ued internally. /// internal void DestroyElement( string elementNameInternal ) { object deadElementWalking = core.elementInfoCores[ elementNameInternal ]; // It's an internal error if we try to call this without first // verifying that it is indeed there. Debug.Assert( null != deadElementWalking, "Caller should have already verified that there's something to delete."); // Can't delete if we're in read-only mode. This catches some but not // all invalid delete scenarios - anything else would come back as a // COMException of some kind that will be caught and wrapped in an // IOException in the try/catch below. if( FileAccess.Read == Root.OpenAccess ) { throw new UnauthorizedAccessException( SR.Get(SRID.CanNotDeleteInReadOnly)); } //Clean out the entry in dataspacemanager for stream transforms DataSpaceManager manager = Root.GetDataSpaceManager(); if( null != manager ) { if( deadElementWalking is StorageInfoCore ) { //if the element getting deleted is a storage, make sure to delete all its children's references. string name = ((StorageInfoCore)deadElementWalking).storageName; StorageInfo stInfo = new StorageInfo(this, name); RemoveSubStorageEntryFromDataSpaceMap(stInfo); } else if( deadElementWalking is StreamInfoCore ) { //if the element getting deleted is a stream, the container reference should be removed from dataspacemap of dataspace manager. manager.RemoveContainerFromDataSpaceMap(new CompoundFileStreamReference( FullNameInternal, elementNameInternal )); } } // Make the call to the underlying OLE mechanism to remove the element. try { core.safeIStorage.DestroyElement( elementNameInternal ); } catch( COMException e ) { if( e.ErrorCode == SafeNativeCompoundFileConstants.STG_E_ACCESSDENIED ) { throw new UnauthorizedAccessException( SR.Get(SRID.CanNotDeleteAccessDenied), e ); } else { throw new IOException( SR.Get(SRID.CanNotDelete), e ); } } // Invalidate enumerators InvalidateEnumerators(); // Remove the now-meaningless name, which also signifies disposed status. if( deadElementWalking is StorageInfoCore ) { StorageInfoCore deadStorageInfoCore = (StorageInfoCore)deadElementWalking; // Erase this storage's existence deadStorageInfoCore.storageName = null; if( null != deadStorageInfoCore.safeIStorage ) { ((IDisposable) deadStorageInfoCore.safeIStorage).Dispose(); deadStorageInfoCore.safeIStorage = null; } } else if( deadElementWalking is StreamInfoCore ) { StreamInfoCore deadStreamInfoCore = (StreamInfoCore)deadElementWalking; // Erase this stream's existence deadStreamInfoCore.streamName = null; try { if (null != deadStreamInfoCore.exposedStream) { ((Stream)(deadStreamInfoCore.exposedStream)).Close(); } } catch(Exception e) { if(CriticalExceptions.IsCriticalException(e)) { // PreSharp Warning 56500 throw; } else { // We don't care if there are any issues - // the user wanted this stream gone anyway. } } deadStreamInfoCore.exposedStream = null; if( null != deadStreamInfoCore.safeIStream ) { ((IDisposable) deadStreamInfoCore.safeIStream).Dispose(); deadStreamInfoCore.safeIStream = null; } } // Remove reference for destroyed element core.elementInfoCores.Remove(elementNameInternal); } ////// Looks for a storage element with the given name, retrieves its /// STATSTG if found. /// /// Name to look for in this storage /// If found, a copy of STATSTG for it ///true if found internal bool FindStatStgOfName( string streamName, out System.Runtime.InteropServices.ComTypes.STATSTG statStg ) { bool nameFound = false; UInt32 actual; IEnumSTATSTG safeIEnumSTATSTG = null; // Set up IEnumSTATSTG core.safeIStorage.EnumElements( 0, IntPtr.Zero, 0, out safeIEnumSTATSTG ); safeIEnumSTATSTG.Reset(); safeIEnumSTATSTG.Next( 1, out statStg, out actual ); // Loop and get everything while( 0 < actual && !nameFound ) { // Stream names: we preserve casing, but do case-insensitive comparison (Native CompoundFile API behavior) if(((IEqualityComparer) CU.StringCaseInsensitiveComparer).Equals(streamName, statStg.pwcsName)) { nameFound = true; } else { // Move on to the next element safeIEnumSTATSTG.Next( 1, out statStg, out actual ); } } // Release enumerator ((IDisposable) safeIEnumSTATSTG).Dispose(); safeIEnumSTATSTG = null; return nameFound; } ////// Find out if a storage is empty of elements /// ///true if storage is empty internal bool StorageIsEmpty() { // Is there a better way than to check if enumerator has nothing? UInt32 actual; IEnumSTATSTG safeIEnumSTATSTG = null; System.Runtime.InteropServices.ComTypes.STATSTG dummySTATSTG; // Set up IEnumSTATSTG core.safeIStorage.EnumElements( 0, IntPtr.Zero, 0, out safeIEnumSTATSTG ); safeIEnumSTATSTG.Reset(); safeIEnumSTATSTG.Next( 1, out dummySTATSTG, out actual ); // Release enumerator ((IDisposable) safeIEnumSTATSTG).Dispose(); safeIEnumSTATSTG = null; // If the first "Next" call returns nothing, then there's nothing here. return ( 0 == actual ); } ////// If anything about this storage has changed, we need to go out and /// invalidate every outstanding enumerator so any attempt to use them /// will result in InvalidOperationException as specified for IEnumerator /// interface implementers /// internal void InvalidateEnumerators() { InvalidateEnumerators( core ); } ////// Given a StorageInfoCore, clears the enumerators associated with it. /// private static void InvalidateEnumerators( StorageInfoCore invalidateCore ) { // It is not enough to simply clear the validEnumerators collection, // we have to clear the individual elements to let them know the // outstanding enumerator is no longer valid. foreach( object entry in invalidateCore.validEnumerators.Values ) { ((ArrayList)entry).Clear(); } invalidateCore.validEnumerators.Clear(); } ////// This will build a full path to this storage from the parent full /// name and our storage name. The array is basically the same as the /// parent storage's Name property plus one element - our /// storageName /// ///ArrayList designating the path of this storage internal ArrayList BuildFullNameFromParentName() { if( null == parentStorage ) { // special case for root storage return new ArrayList(); } else { ArrayList parentArray = parentStorage.BuildFullNameFromParentName(); parentArray.Add(core.storageName); return parentArray; } } ////// Counterpart to BuildFullNameFromParentName that uses the internal /// normalized names instead. /// internal ArrayList BuildFullNameInternalFromParentNameInternal() { if( null == parentStorage ) { // special case for root storage return new ArrayList(); } else { ArrayList parentArray = parentStorage.BuildFullNameInternalFromParentNameInternal(); parentArray.Add(core.storageName); return parentArray; } } ////// This needs to be available to StreamInfo so it can actually create itself /// internal IStorage SafeIStorage { get { VerifyExists(); return core.safeIStorage; } } ////// Every method here have a need to check if the storage exists before /// proceeeding with the operation. However, for reasons I don't fully /// understand we're discouraged from methods calling on other externally /// visible methods, so they can't just call Exists(). So I just pull /// it out to an InternalExists method. /// /// At this time I believe only two methods call this - Exists() because /// it really wants to know, VerifyExists() because it's called by /// everybody else just to see that the storage exists before proceeding. /// /// If this returns true, the storage cache pointer should be live. /// ///Whether "this" storage exists bool InternalExists() { return InternalExists( core.storageName ); } ////// Every method here have a need to check if the storage exists before /// proceeeding with the operation. However, for reasons I don't fully /// understand we're discouraged from methods calling on other externally /// visible methods, so they can't just call Exists(). So I just pull /// it out to an InternalExists method. /// /// At this time I believe only two methods call this - Exists() because /// it really wants to know, VerifyExists() because it's called by /// everybody else just to see that the storage exists before proceeding. /// /// If this returns true, the storage cache pointer should be live. /// ///Whether "this" storage exists bool InternalExists(string name) { // We can't have an IStorage unless we exist. if( null != core.safeIStorage ) { return true; } // If we are the root storage, we always exist. if( null == parentStorage ) { return true; } // If the parent storage does not exist, we can't possibly exist if( !parentStorage.Exists ) { return false; } // Now things get more complicated... we know that: // * We are not the root // * We have a valid parent // * We don't have an IStorage interface // The most obvious way to check is to try opening the storage and // see what happens. It's supposed to be fairly fast and easy // because it stays within the DocFile FAT, and that'll give // us our IStorage cache pointer too. return parentStorage.CanOpenStorage( name ); } bool CanOpenStorage( string nameInternal ) { bool openSuccess = false; StorageInfoCore childCore = core.elementInfoCores[ nameInternal ] as StorageInfoCore ; Debug.Assert( null != childCore, "Expected a child with valid core object in cache" ); int nativeCallErrorCode = 0; nativeCallErrorCode = core.safeIStorage.OpenStorage( nameInternal, null, (GetStat().grfMode & SafeNativeCompoundFileConstants.STGM_READWRITE_Bits) | SafeNativeCompoundFileConstants.STGM_SHARE_EXCLUSIVE, IntPtr.Zero, 0, out childCore.safeIStorage ); if( SafeNativeCompoundFileConstants.S_OK == nativeCallErrorCode ) { openSuccess = true; } else if( SafeNativeCompoundFileConstants.STG_E_FILENOTFOUND != nativeCallErrorCode ) { // Error is not STG_E_FILENOTFOUND, pass it on. throw new IOException( SR.Get(SRID.CanNotOpenStorage), new COMException( SR.Get(SRID.NamedAPIFailure, "IStorage::OpenStorage"), nativeCallErrorCode )); } // STG_E_NOTFOUND - return openSuccess as false return openSuccess; } ////// Most of the time internal methods that want to do an internal check /// to see if a storage exists is only interested in proceeding if it does. /// If it doesn't, abort with an exception. This implements the little /// shortcut. /// void VerifyExists() { if( !InternalExists() ) { throw new DirectoryNotFoundException( SR.Get(SRID.CanNotOnNonExistStorage)); } return; } ////// Grabs the STATSTG representing us /// System.Runtime.InteropServices.ComTypes.STATSTG GetStat() { System.Runtime.InteropServices.ComTypes.STATSTG returnValue; VerifyExists(); core.safeIStorage.Stat( out returnValue, 0 ); return returnValue; } ////// Convert a System.Runtime.InteropServices.FILETIME struct to the CLR /// DateTime class. Strange that the straightforward conversion doesn't /// already exist. Perhaps I'm just not finding it. DateTime has a /// method to convert itself to FILETIME, but only supports creating a /// DateTime from a 64-bit value representing FILETIME instead of the /// FILETIME struct itself. /// DateTime ConvertFILETIMEToDateTime( System.Runtime.InteropServices.ComTypes.FILETIME time ) { // We should let the user know when the time is not valid, rather than // return a bogus date of Dec 31. 1600. if( 0 == time.dwHighDateTime && 0 == time.dwLowDateTime ) throw new NotSupportedException( SR.Get(SRID.TimeStampNotAvailable)); // CLR return DateTime.FromFileTime( (((long)time.dwHighDateTime) << 32) + (uint)time.dwLowDateTime ); // This second uint is very important!! } ////// Given a StorageInfoCore, recursively release objects associated with /// sub-storages and then releases objects associated with self. /// ////// This didn't used to be a static method - but this caused some problems. /// All operations need to be on the given parameter "startCore". If this /// wasn't static, the code had access to the member "core" and that's not /// the right StorageInfoCore to use. This caused some bugs that would have /// been avoided if this was static. To prevent future bugs of similar /// nature, I made this static. /// internal static void RecursiveStorageInfoCoreRelease( StorageInfoCore startCore ) { // If the underlying IStorage pointer is null, we never did anything // with this storage or anything under it. We can halt our recursion // here. if( startCore.safeIStorage == null ) return; try { ////////////////////////////////////////////////////////////////////// // // Call clean-up code for things on the storage represented by startCore. ////////////////////////////////////////////////////////////////////// // // Call clean-up code for things *under* startCore. // See if we have child storages and streams, and if so, close them // down. foreach( object o in startCore.elementInfoCores.Values ) { if( o is StorageInfoCore ) { RecursiveStorageInfoCoreRelease( (StorageInfoCore)o ); } else if( o is StreamInfoCore ) { StreamInfoCore streamRelease = (StreamInfoCore)o; try { if (null != streamRelease.exposedStream) { ((Stream)(streamRelease.exposedStream)).Close(); } streamRelease.exposedStream = null; } finally { // We need this release and null-out to happen even if we // ran into problems with the clean-up code above. if( null != streamRelease.safeIStream) { ((IDisposable) streamRelease.safeIStream).Dispose(); streamRelease.safeIStream = null; } // Null name in core signifies the core object is disposed ((StreamInfoCore)o).streamName = null; } } } // All child objects freed, clear out the enumerators InvalidateEnumerators( startCore ); } finally { // We want to make sure this happens even if any of the cleanup // above fails, so that's why it's in a "finally" block here. ////////////////////////////////////////////////////////////////////// // // Free unmanaged resources associated with the startCore storage if( null != startCore.safeIStorage) { ((IDisposable) startCore.safeIStorage).Dispose(); startCore.safeIStorage = null; } // Null name in core signifies the core object is disposed startCore.storageName = null; } } // Check whether this StorageInfo is still valid. Throw if disposed. internal void CheckDisposedStatus() { // null == parentStorage means we're root. if( StorageDisposed ) throw new ObjectDisposedException(null, SR.Get(SRID.StorageInfoDisposed)); } // Check whether this StorageInfo is still valid. internal bool StorageDisposed { get { // null == parentStorage means we're root. if( null != parentStorage ) { // Check our core reference to see if we're valid if( null == core.storageName ) // Null name in core signifies the core object is disposed { // We have been deleted return true; } // We're not the root storage - check parent. return parentStorage.StorageDisposed; } else if (this is StorageRoot) { return ((StorageRoot)this).RootDisposed; } else { Debug.Assert(rootStorage != null, "Root storage cannot be null if StorageInfo and empty parentStorage"); return rootStorage.RootDisposed; } } } // There is a hash table in the core object core.validEnumerators that // caches the arrays used to hand out the enumerations of this storage // object. A call to this method will ensure that the array exists. If // it already exists, this is a no-op. If it doesn't yet exist, it is // built before we return. // When this function exits, core.validEnumerators(desiredArrayType) will // have an array of the appropriate type. private void EnsureArrayForEnumeration( EnumeratorTypes desiredArrayType ) { Debug.Assert( desiredArrayType == EnumeratorTypes.Everything || desiredArrayType == EnumeratorTypes.OnlyStorages || desiredArrayType == EnumeratorTypes.OnlyStreams, "Invalid type enumeration value is being used to build enumerator array" ); Debug.Assert( InternalExists(), "It is the responsibility of the caller to ensure that storage exists (and is not disposed - which is harder to check at this point so it wasn't done.)"); if( null == core.validEnumerators[ desiredArrayType ] ) { ArrayList storageElems = new ArrayList(); string externalName = null; // Set up IEnumSTATSTG System.Runtime.InteropServices.ComTypes.STATSTG enumElement; UInt32 actual; IEnumSTATSTG safeIEnumSTATSTG = null; core.safeIStorage.EnumElements( 0, IntPtr.Zero, 0, out safeIEnumSTATSTG ); safeIEnumSTATSTG.Reset(); safeIEnumSTATSTG.Next( 1, out enumElement, out actual ); // Loop and get everything while( 0 < actual ) { externalName = enumElement.pwcsName; // In an enumerator, we don't return anything within the reserved // name range. (First character is \x0001 - \x001F) if( CU.IsReservedName(externalName ) ) { ; // Do nothing for reserved names. } else if( SafeNativeCompoundFileConstants.STGTY_STORAGE == enumElement.type ) { if( desiredArrayType == EnumeratorTypes.Everything || desiredArrayType == EnumeratorTypes.OnlyStorages ) { // Add reference to a storage to enumerator array storageElems.Add(new StorageInfo(this, externalName)); } } else if( SafeNativeCompoundFileConstants.STGTY_STREAM == enumElement.type ) { if( desiredArrayType == EnumeratorTypes.Everything || desiredArrayType == EnumeratorTypes.OnlyStreams ) { // Add reference to a stream to enumerator array storageElems.Add(new StreamInfo(this, externalName)); } } else { throw new NotSupportedException( SR.Get(SRID.UnsupportedTypeEncounteredWhenBuildingStgEnum)); } // Move on to the next element safeIEnumSTATSTG.Next( 1, out enumElement, out actual ); } core.validEnumerators[ desiredArrayType ] = storageElems; // Release IEnumSTATSTG ((IDisposable) safeIEnumSTATSTG).Dispose(); safeIEnumSTATSTG = null; } Debug.Assert( null != core.validEnumerators[ desiredArrayType ], "We failed to ensure the proper array for enumeration" ); } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // Class for manipulating storages in the container file. // // History: // 05/13/2002: RogerCh: Initial creation. // 06/25/2002: RogerCh: Data space support. // 07/31/2002: RogerCh: Make obvious that we are using security suppressed interfaces. // 05/19/2003: RogerCh: Port to WCP tree. // 05/28/2003: RogerCh: Added long name support // 06/20/2003: RogerCh: GetStreams() and GetSubStorages() // 08/11/2003: LGolding: Fix Bug 864168 (some of BruceMac's bug fixes were lost // in port to WCP tree). // //----------------------------------------------------------------------------- using System; using System.Collections; using System.ComponentModel; // For EditorBrowsable attribute using System.Diagnostics; // For Assert using System.Security; using System.Security.Permissions; using System.IO; using System.Globalization; // CultureInfo.InvariantCulture using System.Windows; // SR.Get(SRID.[exception message]) using MS.Internal.IO.Packaging.CompoundFile; using CU = MS.Internal.IO.Packaging.CompoundFile.ContainerUtilities; using MS.Internal; // for Invariant & CriticalExceptions using System.Runtime.InteropServices; // COMException #pragma warning disable 1634, 1691 // suppressing PreSharp warnings namespace System.IO.Packaging { ////// This class holds the core information for a StorageInfo object. /// internal class StorageInfoCore { internal StorageInfoCore( string nameStorage ) : this( nameStorage, null ) {;} internal StorageInfoCore( string nameStorage, IStorage storage ) { storageName = nameStorage; safeIStorage = storage; validEnumerators = new Hashtable(); // Storage and Stream names: we preserve casing, but do case-insensitive comparison (Native CompoundFile API behavior) elementInfoCores = new Hashtable(CU.StringCaseInsensitiveComparer); } ////// The compound-file friendly version name. /// internal string storageName; ////// A reference to "this" storage. This value is non-null only when /// 1) The storage exists /// 2) We had reason to open it. /// The value may be null even when the storage exists because we may not /// have need to go and open it. /// internal IStorage safeIStorage; ////// We keep track of the enumerator objects that we've handed out. /// If anything is changed in this storage, we go and invalidate all of /// them and clear the list. /// /// In theory a simple ListDictionary class is more efficient than using /// a Hashtable class. But since we're bringing in the code for the /// HashTable class anyway, the savings of using ListDictionary went away. /// So even with a maximum of three elements, we use a Hashtable. /// internal Hashtable validEnumerators; ////// This hash table holds the standing "core" objects for its child /// elements. Each element may be a StorageInfoCore or a /// StreamInfoCore. /// internal Hashtable elementInfoCores; } ////// Class for manipulating storages in the container file /// public class StorageInfo { /***********************************************************************/ // Instance values ////// Each storage holds a reference to its parent, this way even if the /// client app releases the reference it'll be kept in the reference graph /// to avoid getting prematurely garbage-collected. /// /// The only time this is allowed to be null is when this storage is the /// root storage. /// StorageInfo parentStorage; ////// Each storage holds a reference to the container root. This value will /// be equal to null for the container root. /// StorageRoot rootStorage; ////// There is one StorageInfoCore object per underlying IStorage. If /// multiple StorageInfo objects are created that point to the same /// underlying storage, they share the same StorageInfoCore object. /// These are maintained by the parent storage for all its child /// storages, with the exception of the root StorageInfo which keeps /// its own instance in StorageRoot. /// internal StorageInfoCore core; // Instance name for the compression transform private static readonly string sc_compressionTransformName = "CompressionTransform"; //Dataspace label definitions for compression and encryption combinations while creating a stream private static readonly string sc_dataspaceLabelNoEncryptionNormalCompression = "NoEncryptionNormalCompression"; private static readonly string sc_dataspaceLabelRMEncryptionNormalCompression = "RMEncryptionNormalCompression"; ////// We can have three valid enumerator types. /// private enum EnumeratorTypes { Everything, OnlyStorages, OnlyStreams } /***********************************************************************/ // Constructors ////// A constructor for building the root storage. /// This should only happen for, well, the root storage! /// internal StorageInfo( IStorage safeIStorage ) { core = new StorageInfoCore( null, safeIStorage ); } ////// Given a parent and a path under it, step through each of the path /// elements and create an intermediate StorageInfo at each step because /// a StorageInfo is only meaningful relative to its immediate parent - /// multi-step relations can't be represented. /// private void BuildStorageInfoRelativeToStorage( StorageInfo parent, string fileName ) { parentStorage = parent; core = parent.CoreForChildStorage( fileName ); rootStorage = parent.Root; } ////// Constructor for a StorageInfo given a parent StorageInfo and a name /// /// Reference to the parent storage /// filename for the new StorageInfo internal StorageInfo( StorageInfo parent, string fileName ) { CU.CheckAgainstNull( parent, "parent" ); CU.CheckAgainstNull( fileName, "fileName" ); BuildStorageInfoRelativeToStorage( parent, fileName ); } ////// Respond to a request from a child StorageInfo object to give /// it a StorageInfoCore object for the given name. /// StorageInfoCore CoreForChildStorage( string storageNname ) { CheckDisposedStatus(); object childElement = core.elementInfoCores[ storageNname ]; if( null != childElement && null == childElement as StorageInfoCore ) { // Name is already in use, but not as a StorageInfo throw new InvalidOperationException( SR.Get(SRID.NameAlreadyInUse, storageNname )); } else if( null == childElement ) { // No such element with the name exist - create one. // childElement = new StorageInfoCore( storageNname); core.elementInfoCores[ storageNname ] = childElement; } Debug.Assert( null != childElement as StorageInfoCore, "We should have already checked to make sure childElement is either StorageInfoCore, created as one, or thrown an exception if neither is possible"); return childElement as StorageInfoCore; } internal StreamInfoCore CoreForChildStream( string streamName ) { CheckDisposedStatus(); object childElement = core.elementInfoCores[ streamName ]; if( null != childElement && null == childElement as StreamInfoCore ) { // Name is already in use, but not as a StreamInfo throw new InvalidOperationException( SR.Get(SRID.NameAlreadyInUse, streamName )); } else if( null == childElement ) { // No such element with the name exist - create one. // // Check to see if there is a data space mapping on this guy DataSpaceManager manager = Root.GetDataSpaceManager(); if( null != manager ) { // Have data space manager - retrieve data space label for // the child stream. The data space manager will return // null if there isn't a data space associated with this // stream. childElement = new StreamInfoCore( streamName, manager.DataSpaceOf( new CompoundFileStreamReference( FullNameInternal, streamName ) ) ); } else { // Data space manager not yet initialized - correct behavior // is that nothing transformed should be required at this // point but in case of incorrect behavior we can not possibly // recover. User gets un-transformed data instead. childElement = new StreamInfoCore( streamName, null, null ); } core.elementInfoCores[ streamName ] = childElement; } Debug.Assert( null != childElement as StreamInfoCore, "We should have already checked to make sure childElement is either StreamInfoCore, created as one, or thrown an exception if neither is possible"); return childElement as StreamInfoCore; } /***********************************************************************/ // public Properties ////// The name for this storage /// public string Name { get { CheckDisposedStatus(); return core.storageName; } } /***********************************************************************/ // public Methods ////// Creates "this" stream /// /// Name of stream /// CompressionOptiont /// EncryptionOption ///Reference to new stream public StreamInfo CreateStream( string name, CompressionOption compressionOption, EncryptionOption encryptionOption ) { CheckDisposedStatus(); //check the arguments if( null == name ) throw new ArgumentNullException("name"); // Stream names: we preserve casing, but do case-insensitive comparison (Native CompoundFile API behavior) if (((IEqualityComparer) CU.StringCaseInsensitiveComparer).Equals(name, EncryptedPackageEnvelope.PackageStreamName)) throw new ArgumentException(SR.Get(SRID.StreamNameNotValid,name)); //create a new streaminfo object StreamInfo streamInfo = new StreamInfo(this, name, compressionOption, encryptionOption); if (streamInfo.InternalExists()) { throw new IOException(SR.Get(SRID.StreamAlreadyExist)); } //Define the compression and encryption options in the dataspacemanager DataSpaceManager manager = Root.GetDataSpaceManager(); string dataSpaceLabel = null; if (manager != null) { //case : Compression option is set. Stream need to be compressed. Define compression transform. //At this time, we only treat CompressionOption - Normal and None. The rest are treated as Normal if (compressionOption != CompressionOption.NotCompressed) { //If it is not defined already, define it. if (!manager.TransformLabelIsDefined(sc_compressionTransformName)) manager.DefineTransform(CompressionTransform.ClassTransformIdentifier, sc_compressionTransformName); } //case : Encryption option is set. Stream need to be encrypted. Define encryption transform. if (encryptionOption == EncryptionOption.RightsManagement) { //If it not defined already, define it. if (!manager.TransformLabelIsDefined(EncryptedPackageEnvelope.EncryptionTransformName)) { //We really cannot define RM transform completely here because the transform initialization cannot be done here without publishlicense and cryptoprovider. //However it will always be defined because this method is accessed only through an EncryptedPackageEnvelope and RM transform is always defined in EncryptedPackageEnvelope.Create() throw new SystemException(SR.Get(SRID.RightsManagementEncryptionTransformNotFound)); } } //Now find the dataspace label that we need to define these transforms in. //CASE: When both CompressionOption and EncryptionOption are set if ( (compressionOption != CompressionOption.NotCompressed) && (encryptionOption == EncryptionOption.RightsManagement) ) { dataSpaceLabel = sc_dataspaceLabelRMEncryptionNormalCompression; if (!manager.DataSpaceIsDefined(dataSpaceLabel)) { string[] transformStack = new string[2]; //compress the data first. then encrypt it. This ordering will cause the content to be compressed, then encrypted, then written to the stream. transformStack[0] = EncryptedPackageEnvelope.EncryptionTransformName; transformStack[1] = sc_compressionTransformName; manager.DefineDataSpace(transformStack, dataSpaceLabel); } } //CASE : when CompressionOption alone is set else if ( (compressionOption != CompressionOption.NotCompressed) && (encryptionOption == EncryptionOption.None) ) { dataSpaceLabel = sc_dataspaceLabelNoEncryptionNormalCompression; if (!manager.DataSpaceIsDefined(dataSpaceLabel)) { string[] transformStack = new string[1]; transformStack[0] = sc_compressionTransformName; manager.DefineDataSpace(transformStack, dataSpaceLabel); } } //CASE : when EncryptionOption alone is set else if (encryptionOption == EncryptionOption.RightsManagement) { dataSpaceLabel = EncryptedPackageEnvelope.DataspaceLabelRMEncryptionNoCompression; if (!manager.DataSpaceIsDefined(dataSpaceLabel)) { string[] transformStack = new string[1]; transformStack[0] = EncryptedPackageEnvelope.EncryptionTransformName; manager.DefineDataSpace(transformStack, dataSpaceLabel); } } //All the other cases are not handled at this point. } //create the underlying stream if (null == dataSpaceLabel) streamInfo.Create(); //create the stream with default parameters else streamInfo.Create(dataSpaceLabel); //create the stream in the defined dataspace return streamInfo; } ////// Creates "this" stream /// /// Name of stream ///Reference to new stream public StreamInfo CreateStream( string name ) { //create the stream with out any compression or encryption options. return CreateStream(name,CompressionOption.NotCompressed,EncryptionOption.None); } ////// Returns the streaminfo by the passed name. /// /// Name of stream ///Reference to the stream public StreamInfo GetStreamInfo(string name) { CheckDisposedStatus(); //check the arguments if( null == name ) throw new ArgumentNullException("name"); StreamInfo streamInfo = new StreamInfo(this, name); if (streamInfo.InternalExists()) { return streamInfo; } else { throw new IOException(SR.Get(SRID.StreamNotExist)); } } ////// Check if the stream exists. /// /// Name of stream ///True if exists, False if not public bool StreamExists(string name) { CheckDisposedStatus(); bool streamExists = false; StreamInfo streamInfo = new StreamInfo(this, name); streamExists = streamInfo.InternalExists(); return streamExists; } ////// Deleted the stream with the passed name. /// /// Name of stream public void DeleteStream(string name) { CheckDisposedStatus(); //check the arguments if( null == name ) throw new ArgumentNullException("name"); StreamInfo streamInfo = new StreamInfo(this, name); if (streamInfo.InternalExists()) { streamInfo.Delete(); } } ////// Creates a storage using "this" one as parent /// /// Name of new storage ///Reference to new storage public StorageInfo CreateSubStorage( string name ) { CheckDisposedStatus(); //check the arguments if( null == name ) throw new ArgumentNullException("name"); return CreateStorage(name); } ////// Returns the storage by the passed name. /// /// Name of storage ///Reference to the storage public StorageInfo GetSubStorageInfo(string name) { //Find if this storage exists StorageInfo storageInfo = new StorageInfo(this, name); if (storageInfo.InternalExists(name)) { return storageInfo; } else { throw new IOException(SR.Get(SRID.StorageNotExist)); } } ////// Checks if a storage exists by the passed name. /// /// Name of storage ///Reference to new storage public bool SubStorageExists(string name) { StorageInfo storageInfo = new StorageInfo(this, name); return storageInfo.InternalExists(name); } ////// Deletes a storage recursively. /// /// Name of storage public void DeleteSubStorage(string name) { CheckDisposedStatus(); //check the arguments if( null == name ) throw new ArgumentNullException("name"); StorageInfo storageInfo = new StorageInfo(this, name); if (storageInfo.InternalExists(name)) { InvalidateEnumerators(); // Go ahead and delete "this" storage DestroyElement( name ); } //We will not throw exceptions if the storage does not exist. This is to be consistent with Package.DeletePart. } ////// Provides a snapshot picture of the streams currently within this storage /// object. The array that returns will not be updated with additions/ /// removals of streams, but the individual StreamInfo objects within will /// reflect the state of their respective streams. /// ////// This follows the precedent of DirectoryInfo.GetFiles() /// ////// Array of StreamInfo objects, each pointing to a stream within this /// storage. Empty (zero-length) array if there are no streams. /// public StreamInfo[] GetStreams() { // Make sure 'this' storage is alive and well. CheckDisposedStatus(); VerifyExists(); // Build an array of StreamInfo objects EnsureArrayForEnumeration(EnumeratorTypes.OnlyStreams); // Because we're handing out a snapshot, we can't simply hand out a // reference to the core.validEnumerators array. We need to make a // copy that has the references of the array. This way the array // we return will remain if we invalidate the arrays. // Fortunately ArrayList.ToArray makes a copy, perfect for our needs. ArrayList streamArray = (ArrayList)core.validEnumerators[EnumeratorTypes.OnlyStreams]; Invariant.Assert(streamArray != null); #pragma warning suppress 6506 // Invariant.Assert(streamArray != null) return (StreamInfo[])streamArray.ToArray(typeof(StreamInfo)); } ////// Provides a snapshot picture of the sub-storages currently within this storage /// object. The array that returns will not be updated with additions/ /// removals of storages, but the individual StorageInfo objects within will /// reflect the state of their respective sub-storages. /// ////// This follows the precedent of DirectoryInfo.GetDirectories() /// ////// Array of StorageInfo objects, each pointing to a sub-storage within this /// storage. Empty (zero-length) array if there are no sub-storages. /// public StorageInfo[] GetSubStorages() { // Make sure 'this' storage is alive and well. CheckDisposedStatus(); VerifyExists(); // See GetStreams counterpart for details. EnsureArrayForEnumeration(EnumeratorTypes.OnlyStorages); ArrayList storageArray = (ArrayList)core.validEnumerators[EnumeratorTypes.OnlyStorages]; Invariant.Assert(storageArray != null); #pragma warning suppress 6506 // Invariant.Assert(streamArray != null) return (StorageInfo[])storageArray.ToArray(typeof(StorageInfo)); } /***********************************************************************/ // Internal/Private functionality internal string FullNameInternal { get { CheckDisposedStatus(); return CU.ConvertStringArrayPathToBackSlashPath(BuildFullNameInternalFromParentNameInternal()); } } ////// Get a reference to the container instance /// internal StorageRoot Root { get { CheckDisposedStatus(); if( null == rootStorage ) return (StorageRoot)this; else return rootStorage; } } ////// Because it is valid to have a StorageInfo point to a storage that /// doesn't yet actually exist, use this to see if it does. /// internal bool Exists { get { CheckDisposedStatus(); return InternalExists(); } } ////// Creates "this" storage /// internal void Create() { CheckDisposedStatus(); if( null != parentStorage ) // Only root storage has null parentStorage { // Root storage always exists so we don't do any of this // if we're not the root. if( !parentStorage.Exists ) { // We need the parent to exist before we can exist. parentStorage.Create(); } if( !InternalExists() ) { // If we don't exist, then ask parent to create us. parentStorage.CreateStorage( core.storageName ); } //else if we already exist, we're done here. } } private StorageInfo CreateStorage(string name) { // Create new StorageInfo StorageInfo newSubStorage = new StorageInfo( this, name ); // Make it real if( !newSubStorage.InternalExists(name) ) { /* TBD if( !CU.IsValidCompoundFileName(name)) { throw new IOException( SR.Get(SRID.UnableToCreateStorage), new COMException( SR.Get(SRID.NamedAPIFailure, "IStorage.CreateStorage"), nativeCallErrorCode )); } */ // It doesn't already exist, please create. StorageInfoCore newStorage = core.elementInfoCores[ name ] as StorageInfoCore; Invariant.Assert( null != newStorage); int nativeCallErrorCode = core.safeIStorage.CreateStorage( name, (GetStat().grfMode & SafeNativeCompoundFileConstants.STGM_READWRITE_Bits) | SafeNativeCompoundFileConstants.STGM_SHARE_EXCLUSIVE, 0, 0, #pragma warning suppress 6506 // Invariant.Assert(null != newStorage) out newStorage.safeIStorage ); if( SafeNativeCompoundFileConstants.S_OK != nativeCallErrorCode ) { if( nativeCallErrorCode == SafeNativeCompoundFileConstants.STG_E_ACCESSDENIED ) { throw new UnauthorizedAccessException( SR.Get(SRID.CanNotCreateAccessDenied), new COMException( SR.Get(SRID.NamedAPIFailure, "IStorage.CreateStorage"), nativeCallErrorCode )); } else { throw new IOException( SR.Get(SRID.UnableToCreateStorage), new COMException( SR.Get(SRID.NamedAPIFailure, "IStorage.CreateStorage"), nativeCallErrorCode )); } } // Invalidate enumerators InvalidateEnumerators(); } else { throw new IOException(SR.Get(SRID.StorageAlreadyExist)); } // Return a reference return newSubStorage; } ////// Deletes a storage, recursively if specified. /// /// Whether to recursive delete all existing content /// Name of storage internal bool Delete( bool recursive , string name) { bool storageDeleted = false; CheckDisposedStatus(); if( null == parentStorage ) { // We are the root storage, you can't "delete" the root storage! throw new InvalidOperationException( SR.Get(SRID.CanNotDeleteRoot)); } if( InternalExists(name) ) { if( !recursive && !StorageIsEmpty()) { throw new IOException( SR.Get(SRID.CanNotDeleteNonEmptyStorage)); } InvalidateEnumerators(); // Go ahead and delete "this" storage parentStorage.DestroyElement( name ); storageDeleted = true; } //We will not throw exceptions if the storage does not exist. This is to be consistent with Package.DeletePart. return storageDeleted; } ////// When a substorage is getting deleted, its references in the dataspacemanager's transform definition are removed. /// This is called recursively because the DeleteSubStorage deletes all its children by default. /// internal void RemoveSubStorageEntryFromDataSpaceMap(StorageInfo storageInfo) { StorageInfo[] subStorages = storageInfo.GetSubStorages(); foreach(StorageInfo storage in subStorages) { //If this is a storage, call recursively till we encounter a stream. Then we can use that container (storage,stream) reference to remove from the // dataspace manager's data space map. RemoveSubStorageEntryFromDataSpaceMap(storage); //recursive call } //Now we have StorageInfo. Find if there is a stream underneath so a container can be used as reference in data space map of data spacemanager. StreamInfo[] streams = storageInfo.GetStreams(); DataSpaceManager manager = Root.GetDataSpaceManager(); foreach(StreamInfo stream in streams) { manager.RemoveContainerFromDataSpaceMap(new CompoundFileStreamReference( storageInfo.FullNameInternal, stream.Name )); } } ////// Destroys an element and removes the references ued internally. /// internal void DestroyElement( string elementNameInternal ) { object deadElementWalking = core.elementInfoCores[ elementNameInternal ]; // It's an internal error if we try to call this without first // verifying that it is indeed there. Debug.Assert( null != deadElementWalking, "Caller should have already verified that there's something to delete."); // Can't delete if we're in read-only mode. This catches some but not // all invalid delete scenarios - anything else would come back as a // COMException of some kind that will be caught and wrapped in an // IOException in the try/catch below. if( FileAccess.Read == Root.OpenAccess ) { throw new UnauthorizedAccessException( SR.Get(SRID.CanNotDeleteInReadOnly)); } //Clean out the entry in dataspacemanager for stream transforms DataSpaceManager manager = Root.GetDataSpaceManager(); if( null != manager ) { if( deadElementWalking is StorageInfoCore ) { //if the element getting deleted is a storage, make sure to delete all its children's references. string name = ((StorageInfoCore)deadElementWalking).storageName; StorageInfo stInfo = new StorageInfo(this, name); RemoveSubStorageEntryFromDataSpaceMap(stInfo); } else if( deadElementWalking is StreamInfoCore ) { //if the element getting deleted is a stream, the container reference should be removed from dataspacemap of dataspace manager. manager.RemoveContainerFromDataSpaceMap(new CompoundFileStreamReference( FullNameInternal, elementNameInternal )); } } // Make the call to the underlying OLE mechanism to remove the element. try { core.safeIStorage.DestroyElement( elementNameInternal ); } catch( COMException e ) { if( e.ErrorCode == SafeNativeCompoundFileConstants.STG_E_ACCESSDENIED ) { throw new UnauthorizedAccessException( SR.Get(SRID.CanNotDeleteAccessDenied), e ); } else { throw new IOException( SR.Get(SRID.CanNotDelete), e ); } } // Invalidate enumerators InvalidateEnumerators(); // Remove the now-meaningless name, which also signifies disposed status. if( deadElementWalking is StorageInfoCore ) { StorageInfoCore deadStorageInfoCore = (StorageInfoCore)deadElementWalking; // Erase this storage's existence deadStorageInfoCore.storageName = null; if( null != deadStorageInfoCore.safeIStorage ) { ((IDisposable) deadStorageInfoCore.safeIStorage).Dispose(); deadStorageInfoCore.safeIStorage = null; } } else if( deadElementWalking is StreamInfoCore ) { StreamInfoCore deadStreamInfoCore = (StreamInfoCore)deadElementWalking; // Erase this stream's existence deadStreamInfoCore.streamName = null; try { if (null != deadStreamInfoCore.exposedStream) { ((Stream)(deadStreamInfoCore.exposedStream)).Close(); } } catch(Exception e) { if(CriticalExceptions.IsCriticalException(e)) { // PreSharp Warning 56500 throw; } else { // We don't care if there are any issues - // the user wanted this stream gone anyway. } } deadStreamInfoCore.exposedStream = null; if( null != deadStreamInfoCore.safeIStream ) { ((IDisposable) deadStreamInfoCore.safeIStream).Dispose(); deadStreamInfoCore.safeIStream = null; } } // Remove reference for destroyed element core.elementInfoCores.Remove(elementNameInternal); } ////// Looks for a storage element with the given name, retrieves its /// STATSTG if found. /// /// Name to look for in this storage /// If found, a copy of STATSTG for it ///true if found internal bool FindStatStgOfName( string streamName, out System.Runtime.InteropServices.ComTypes.STATSTG statStg ) { bool nameFound = false; UInt32 actual; IEnumSTATSTG safeIEnumSTATSTG = null; // Set up IEnumSTATSTG core.safeIStorage.EnumElements( 0, IntPtr.Zero, 0, out safeIEnumSTATSTG ); safeIEnumSTATSTG.Reset(); safeIEnumSTATSTG.Next( 1, out statStg, out actual ); // Loop and get everything while( 0 < actual && !nameFound ) { // Stream names: we preserve casing, but do case-insensitive comparison (Native CompoundFile API behavior) if(((IEqualityComparer) CU.StringCaseInsensitiveComparer).Equals(streamName, statStg.pwcsName)) { nameFound = true; } else { // Move on to the next element safeIEnumSTATSTG.Next( 1, out statStg, out actual ); } } // Release enumerator ((IDisposable) safeIEnumSTATSTG).Dispose(); safeIEnumSTATSTG = null; return nameFound; } ////// Find out if a storage is empty of elements /// ///true if storage is empty internal bool StorageIsEmpty() { // Is there a better way than to check if enumerator has nothing? UInt32 actual; IEnumSTATSTG safeIEnumSTATSTG = null; System.Runtime.InteropServices.ComTypes.STATSTG dummySTATSTG; // Set up IEnumSTATSTG core.safeIStorage.EnumElements( 0, IntPtr.Zero, 0, out safeIEnumSTATSTG ); safeIEnumSTATSTG.Reset(); safeIEnumSTATSTG.Next( 1, out dummySTATSTG, out actual ); // Release enumerator ((IDisposable) safeIEnumSTATSTG).Dispose(); safeIEnumSTATSTG = null; // If the first "Next" call returns nothing, then there's nothing here. return ( 0 == actual ); } ////// If anything about this storage has changed, we need to go out and /// invalidate every outstanding enumerator so any attempt to use them /// will result in InvalidOperationException as specified for IEnumerator /// interface implementers /// internal void InvalidateEnumerators() { InvalidateEnumerators( core ); } ////// Given a StorageInfoCore, clears the enumerators associated with it. /// private static void InvalidateEnumerators( StorageInfoCore invalidateCore ) { // It is not enough to simply clear the validEnumerators collection, // we have to clear the individual elements to let them know the // outstanding enumerator is no longer valid. foreach( object entry in invalidateCore.validEnumerators.Values ) { ((ArrayList)entry).Clear(); } invalidateCore.validEnumerators.Clear(); } ////// This will build a full path to this storage from the parent full /// name and our storage name. The array is basically the same as the /// parent storage's Name property plus one element - our /// storageName /// ///ArrayList designating the path of this storage internal ArrayList BuildFullNameFromParentName() { if( null == parentStorage ) { // special case for root storage return new ArrayList(); } else { ArrayList parentArray = parentStorage.BuildFullNameFromParentName(); parentArray.Add(core.storageName); return parentArray; } } ////// Counterpart to BuildFullNameFromParentName that uses the internal /// normalized names instead. /// internal ArrayList BuildFullNameInternalFromParentNameInternal() { if( null == parentStorage ) { // special case for root storage return new ArrayList(); } else { ArrayList parentArray = parentStorage.BuildFullNameInternalFromParentNameInternal(); parentArray.Add(core.storageName); return parentArray; } } ////// This needs to be available to StreamInfo so it can actually create itself /// internal IStorage SafeIStorage { get { VerifyExists(); return core.safeIStorage; } } ////// Every method here have a need to check if the storage exists before /// proceeeding with the operation. However, for reasons I don't fully /// understand we're discouraged from methods calling on other externally /// visible methods, so they can't just call Exists(). So I just pull /// it out to an InternalExists method. /// /// At this time I believe only two methods call this - Exists() because /// it really wants to know, VerifyExists() because it's called by /// everybody else just to see that the storage exists before proceeding. /// /// If this returns true, the storage cache pointer should be live. /// ///Whether "this" storage exists bool InternalExists() { return InternalExists( core.storageName ); } ////// Every method here have a need to check if the storage exists before /// proceeeding with the operation. However, for reasons I don't fully /// understand we're discouraged from methods calling on other externally /// visible methods, so they can't just call Exists(). So I just pull /// it out to an InternalExists method. /// /// At this time I believe only two methods call this - Exists() because /// it really wants to know, VerifyExists() because it's called by /// everybody else just to see that the storage exists before proceeding. /// /// If this returns true, the storage cache pointer should be live. /// ///Whether "this" storage exists bool InternalExists(string name) { // We can't have an IStorage unless we exist. if( null != core.safeIStorage ) { return true; } // If we are the root storage, we always exist. if( null == parentStorage ) { return true; } // If the parent storage does not exist, we can't possibly exist if( !parentStorage.Exists ) { return false; } // Now things get more complicated... we know that: // * We are not the root // * We have a valid parent // * We don't have an IStorage interface // The most obvious way to check is to try opening the storage and // see what happens. It's supposed to be fairly fast and easy // because it stays within the DocFile FAT, and that'll give // us our IStorage cache pointer too. return parentStorage.CanOpenStorage( name ); } bool CanOpenStorage( string nameInternal ) { bool openSuccess = false; StorageInfoCore childCore = core.elementInfoCores[ nameInternal ] as StorageInfoCore ; Debug.Assert( null != childCore, "Expected a child with valid core object in cache" ); int nativeCallErrorCode = 0; nativeCallErrorCode = core.safeIStorage.OpenStorage( nameInternal, null, (GetStat().grfMode & SafeNativeCompoundFileConstants.STGM_READWRITE_Bits) | SafeNativeCompoundFileConstants.STGM_SHARE_EXCLUSIVE, IntPtr.Zero, 0, out childCore.safeIStorage ); if( SafeNativeCompoundFileConstants.S_OK == nativeCallErrorCode ) { openSuccess = true; } else if( SafeNativeCompoundFileConstants.STG_E_FILENOTFOUND != nativeCallErrorCode ) { // Error is not STG_E_FILENOTFOUND, pass it on. throw new IOException( SR.Get(SRID.CanNotOpenStorage), new COMException( SR.Get(SRID.NamedAPIFailure, "IStorage::OpenStorage"), nativeCallErrorCode )); } // STG_E_NOTFOUND - return openSuccess as false return openSuccess; } ////// Most of the time internal methods that want to do an internal check /// to see if a storage exists is only interested in proceeding if it does. /// If it doesn't, abort with an exception. This implements the little /// shortcut. /// void VerifyExists() { if( !InternalExists() ) { throw new DirectoryNotFoundException( SR.Get(SRID.CanNotOnNonExistStorage)); } return; } ////// Grabs the STATSTG representing us /// System.Runtime.InteropServices.ComTypes.STATSTG GetStat() { System.Runtime.InteropServices.ComTypes.STATSTG returnValue; VerifyExists(); core.safeIStorage.Stat( out returnValue, 0 ); return returnValue; } ////// Convert a System.Runtime.InteropServices.FILETIME struct to the CLR /// DateTime class. Strange that the straightforward conversion doesn't /// already exist. Perhaps I'm just not finding it. DateTime has a /// method to convert itself to FILETIME, but only supports creating a /// DateTime from a 64-bit value representing FILETIME instead of the /// FILETIME struct itself. /// DateTime ConvertFILETIMEToDateTime( System.Runtime.InteropServices.ComTypes.FILETIME time ) { // We should let the user know when the time is not valid, rather than // return a bogus date of Dec 31. 1600. if( 0 == time.dwHighDateTime && 0 == time.dwLowDateTime ) throw new NotSupportedException( SR.Get(SRID.TimeStampNotAvailable)); // CLR return DateTime.FromFileTime( (((long)time.dwHighDateTime) << 32) + (uint)time.dwLowDateTime ); // This second uint is very important!! } ////// Given a StorageInfoCore, recursively release objects associated with /// sub-storages and then releases objects associated with self. /// ////// This didn't used to be a static method - but this caused some problems. /// All operations need to be on the given parameter "startCore". If this /// wasn't static, the code had access to the member "core" and that's not /// the right StorageInfoCore to use. This caused some bugs that would have /// been avoided if this was static. To prevent future bugs of similar /// nature, I made this static. /// internal static void RecursiveStorageInfoCoreRelease( StorageInfoCore startCore ) { // If the underlying IStorage pointer is null, we never did anything // with this storage or anything under it. We can halt our recursion // here. if( startCore.safeIStorage == null ) return; try { ////////////////////////////////////////////////////////////////////// // // Call clean-up code for things on the storage represented by startCore. ////////////////////////////////////////////////////////////////////// // // Call clean-up code for things *under* startCore. // See if we have child storages and streams, and if so, close them // down. foreach( object o in startCore.elementInfoCores.Values ) { if( o is StorageInfoCore ) { RecursiveStorageInfoCoreRelease( (StorageInfoCore)o ); } else if( o is StreamInfoCore ) { StreamInfoCore streamRelease = (StreamInfoCore)o; try { if (null != streamRelease.exposedStream) { ((Stream)(streamRelease.exposedStream)).Close(); } streamRelease.exposedStream = null; } finally { // We need this release and null-out to happen even if we // ran into problems with the clean-up code above. if( null != streamRelease.safeIStream) { ((IDisposable) streamRelease.safeIStream).Dispose(); streamRelease.safeIStream = null; } // Null name in core signifies the core object is disposed ((StreamInfoCore)o).streamName = null; } } } // All child objects freed, clear out the enumerators InvalidateEnumerators( startCore ); } finally { // We want to make sure this happens even if any of the cleanup // above fails, so that's why it's in a "finally" block here. ////////////////////////////////////////////////////////////////////// // // Free unmanaged resources associated with the startCore storage if( null != startCore.safeIStorage) { ((IDisposable) startCore.safeIStorage).Dispose(); startCore.safeIStorage = null; } // Null name in core signifies the core object is disposed startCore.storageName = null; } } // Check whether this StorageInfo is still valid. Throw if disposed. internal void CheckDisposedStatus() { // null == parentStorage means we're root. if( StorageDisposed ) throw new ObjectDisposedException(null, SR.Get(SRID.StorageInfoDisposed)); } // Check whether this StorageInfo is still valid. internal bool StorageDisposed { get { // null == parentStorage means we're root. if( null != parentStorage ) { // Check our core reference to see if we're valid if( null == core.storageName ) // Null name in core signifies the core object is disposed { // We have been deleted return true; } // We're not the root storage - check parent. return parentStorage.StorageDisposed; } else if (this is StorageRoot) { return ((StorageRoot)this).RootDisposed; } else { Debug.Assert(rootStorage != null, "Root storage cannot be null if StorageInfo and empty parentStorage"); return rootStorage.RootDisposed; } } } // There is a hash table in the core object core.validEnumerators that // caches the arrays used to hand out the enumerations of this storage // object. A call to this method will ensure that the array exists. If // it already exists, this is a no-op. If it doesn't yet exist, it is // built before we return. // When this function exits, core.validEnumerators(desiredArrayType) will // have an array of the appropriate type. private void EnsureArrayForEnumeration( EnumeratorTypes desiredArrayType ) { Debug.Assert( desiredArrayType == EnumeratorTypes.Everything || desiredArrayType == EnumeratorTypes.OnlyStorages || desiredArrayType == EnumeratorTypes.OnlyStreams, "Invalid type enumeration value is being used to build enumerator array" ); Debug.Assert( InternalExists(), "It is the responsibility of the caller to ensure that storage exists (and is not disposed - which is harder to check at this point so it wasn't done.)"); if( null == core.validEnumerators[ desiredArrayType ] ) { ArrayList storageElems = new ArrayList(); string externalName = null; // Set up IEnumSTATSTG System.Runtime.InteropServices.ComTypes.STATSTG enumElement; UInt32 actual; IEnumSTATSTG safeIEnumSTATSTG = null; core.safeIStorage.EnumElements( 0, IntPtr.Zero, 0, out safeIEnumSTATSTG ); safeIEnumSTATSTG.Reset(); safeIEnumSTATSTG.Next( 1, out enumElement, out actual ); // Loop and get everything while( 0 < actual ) { externalName = enumElement.pwcsName; // In an enumerator, we don't return anything within the reserved // name range. (First character is \x0001 - \x001F) if( CU.IsReservedName(externalName ) ) { ; // Do nothing for reserved names. } else if( SafeNativeCompoundFileConstants.STGTY_STORAGE == enumElement.type ) { if( desiredArrayType == EnumeratorTypes.Everything || desiredArrayType == EnumeratorTypes.OnlyStorages ) { // Add reference to a storage to enumerator array storageElems.Add(new StorageInfo(this, externalName)); } } else if( SafeNativeCompoundFileConstants.STGTY_STREAM == enumElement.type ) { if( desiredArrayType == EnumeratorTypes.Everything || desiredArrayType == EnumeratorTypes.OnlyStreams ) { // Add reference to a stream to enumerator array storageElems.Add(new StreamInfo(this, externalName)); } } else { throw new NotSupportedException( SR.Get(SRID.UnsupportedTypeEncounteredWhenBuildingStgEnum)); } // Move on to the next element safeIEnumSTATSTG.Next( 1, out enumElement, out actual ); } core.validEnumerators[ desiredArrayType ] = storageElems; // Release IEnumSTATSTG ((IDisposable) safeIEnumSTATSTG).Dispose(); safeIEnumSTATSTG = null; } Debug.Assert( null != core.validEnumerators[ desiredArrayType ], "We failed to ensure the proper array for enumeration" ); } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- BaseTemplateParser.cs
- __Filters.cs
- DbModificationCommandTree.cs
- ScrollViewer.cs
- ComboBox.cs
- MultiTrigger.cs
- WindowsMenu.cs
- MergeFilterQuery.cs
- SortedList.cs
- SequenceDesigner.xaml.cs
- ColorAnimation.cs
- LinqExpressionNormalizer.cs
- MD5.cs
- AlternateViewCollection.cs
- TypeConverterHelper.cs
- Win32Exception.cs
- FocusChangedEventArgs.cs
- EntityKey.cs
- ColorContextHelper.cs
- PasswordValidationException.cs
- thaishape.cs
- LabelInfo.cs
- DomNameTable.cs
- RuleSet.cs
- SecurityCriticalDataForSet.cs
- ObfuscationAttribute.cs
- LocalsItemDescription.cs
- DesignerVerb.cs
- XmlNodeChangedEventArgs.cs
- RootNamespaceAttribute.cs
- DbParameterHelper.cs
- XPathQilFactory.cs
- ConfigXmlSignificantWhitespace.cs
- DockAndAnchorLayout.cs
- DataGridPagerStyle.cs
- Models.cs
- CalendarBlackoutDatesCollection.cs
- TransactionManagerProxy.cs
- CreateUserWizardAutoFormat.cs
- PriorityChain.cs
- ServiceModelExtensionCollectionElement.cs
- DataControlField.cs
- StorageFunctionMapping.cs
- CodeTypeReferenceExpression.cs
- Int64Converter.cs
- XmlElement.cs
- WebPartConnection.cs
- ClientEventManager.cs
- storepermissionattribute.cs
- CounterSample.cs
- CalendarAutoFormat.cs
- MembershipValidatePasswordEventArgs.cs
- PersonalizationProviderHelper.cs
- SystemInformation.cs
- WebPartVerb.cs
- WaveHeader.cs
- DrawingContextWalker.cs
- HexParser.cs
- EnumerableWrapperWeakToStrong.cs
- ProcessModelSection.cs
- ButtonPopupAdapter.cs
- IdentifierCollection.cs
- SamlAuthenticationClaimResource.cs
- ArrayListCollectionBase.cs
- Function.cs
- CornerRadius.cs
- PriorityItem.cs
- TypeUsage.cs
- CodeMemberMethod.cs
- FocusWithinProperty.cs
- Gdiplus.cs
- XsltArgumentList.cs
- SpellCheck.cs
- AuditLogLocation.cs
- PassportAuthentication.cs
- webeventbuffer.cs
- TagMapInfo.cs
- HotSpot.cs
- StringUtil.cs
- RolePrincipal.cs
- DataSourceCacheDurationConverter.cs
- WebPartConnection.cs
- TextEffectCollection.cs
- TransformerInfo.cs
- MD5HashHelper.cs
- Grant.cs
- HttpAsyncResult.cs
- WebControlAdapter.cs
- TraceInternal.cs
- TcpServerChannel.cs
- WebSysDisplayNameAttribute.cs
- BamlResourceContent.cs
- InstallerTypeAttribute.cs
- PiiTraceSource.cs
- XAMLParseException.cs
- PropertyDescriptorComparer.cs
- SessionStateContainer.cs
- IndicFontClient.cs
- MarkupCompilePass1.cs
- StrongNameIdentityPermission.cs