|
//-----------------------------------------------------------------------------
//
// <copyright file="StreamInfo.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved.
// </copyright>
//
// Description:
// Class for manipulating streams in the container file
//
// History:
// 05/13/2002: RogerCh: Initial implementation.
// 06/25/2002: RogerCh: Data space support.
// 07/31/2002: RogerCh: Make obvious that we are using security suppressed interfaces.
// 05/20/2003: RogerCh: Ported to WCP tree.
// 05/28/2003: RogerCh: Added long name support
//
//-----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics; // For Debug.Assert
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Windows; // SR.Get(SRID.[exception message])
using MS.Internal.IO.Packaging.CompoundFile;
using CU = MS.Internal.IO.Packaging.CompoundFile.ContainerUtilities;
using System.IO.Packaging;
using MS.Internal.WindowsBase;
namespace System.IO.Packaging
{
/// <summary>
/// Core information for a StreamInfo object.
/// </summary>
internal class StreamInfoCore
{
internal StreamInfoCore(
string nameStream,
string label ) : this( nameStream, label, null ) {;}
internal StreamInfoCore(
string nameStream,
string label,
IStream s )
{
streamName = nameStream;
dataSpaceLabel = label;
safeIStream = s;
exposedStream = null;
}
/// <summary>
/// The compound-file friendly version of streamName.
/// </summary>
internal string streamName;
/// <summary>
/// A cached reference to the stream object for accessing the data This
/// may be null if we haven't had need to open the stream.
/// </summary>
internal IStream safeIStream;
/// <summary>
/// The label for the data space definition that is associated with this
/// stream. This can only be set at the time of StreamInfo.Create(). A
/// null string indicates that we are not in a data space.
/// </summary>
internal string dataSpaceLabel;
/// <summary>
/// This represents visible stream object. When the stream represented by this StreamInfo is supposed
/// to go away, this will be reset to null.
/// </summary>
internal object exposedStream;
}
/// <summary>
/// Class for manipulating streams in the container file
/// </summary>
public class StreamInfo
{
/***********************************************************************/
// Default values to use for shortcuts
const FileMode defaultFileOpenMode = FileMode.OpenOrCreate;
const FileMode defaultFileCreateMode = FileMode.Create;
const string defaultDataSpace = null; // Programmatic change-able?
/***********************************************************************/
// Instance values
/// <summary>
/// A reference back to the parent storage object
/// </summary>
StorageInfo parentStorage;
/// <summary>
/// Reference to a class that contains our core information. This is
/// maintained by our parent storage.
/// </summary>
StreamInfoCore core;
/// <summary>
/// CompoundFileStreamReference for this StreamInfo object
/// </summary>
CompoundFileStreamReference _streamReference;
/// <summary>
/// We need to rememeber the FileAccess that was used for openning
/// in order to provide correct information, we can not used underlying structures,
/// as the same stream can be subsequently opened in different modes
/// </summary>
private FileAccess openFileAccess;
private CompressionOption _compressionOption;
private EncryptionOption _encryptionOption;
private bool _needToGetTransformInfo = true;
/***********************************************************************/
// Constructors
private void BuildStreamInfoRelativeToStorage( StorageInfo parent, string path )
{
parentStorage = parent;
core = parentStorage.CoreForChildStream( path );
}
/// <summary>
/// Creates a new instance relative to the root
/// </summary>
/// <param name="root">The root storage</param>
/// <param name="streamPath">Path to stream under root storage</param>
private StreamInfo( StorageRoot root, string streamPath ) : this((StorageInfo)root, streamPath)
{
}
/// <summary>
/// Creates a new instance relative to the given parent
/// </summary>
/// <param name="parent">The parent storage</param>
/// <param name="streamName">Path to stream under parent storage</param>
internal StreamInfo( StorageInfo parent, string streamName ) : this (parent, streamName, CompressionOption.NotCompressed, EncryptionOption.None)
{
}
/// <summary>
/// Creates a new instance relative to the given parent
/// </summary>
/// <param name="parent">The parent storage</param>
/// <param name="streamName">Path to stream under parent storage</param>
/// <param name="compressionOption">CompressionOption</param>
/// <param name="encryptionOption">EncryptionOption</param>
internal StreamInfo( StorageInfo parent, string streamName, CompressionOption compressionOption,
EncryptionOption encryptionOption )
{
// Parameter validation
CU.CheckAgainstNull( parent, "parent" );
CU.CheckStringAgainstNullAndEmpty( streamName, "streamName" );
// Parse path relative to given parent.
BuildStreamInfoRelativeToStorage( parent,
streamName);
_compressionOption = compressionOption;
_encryptionOption = encryptionOption;
_streamReference = new CompoundFileStreamReference(this.parentStorage.FullNameInternal, this.core.streamName);
}
/***********************************************************************/
// Properties
/// <summary>
/// The CompressionOption on the stream
/// </summary>
public CompressionOption CompressionOption
{
get
{
if( StreamInfoDisposed ) // Null name in core signifies the core object is disposed
{
// The .Net Design Guidelines instruct us not to throw exceptions in property getters.
return CompressionOption.NotCompressed;
}
EnsureTransformInformation();
return _compressionOption;
}
}
/// <summary>
/// The EncryptionOption on the stream
/// </summary>
public EncryptionOption EncryptionOption
{
get
{
if( StreamInfoDisposed ) // Null name in core signifies the core object is disposed
{
// The .Net Design Guidelines instruct us not to throw exceptions in property getters.
return EncryptionOption.None;
}
EnsureTransformInformation();
return _encryptionOption;
}
}
/// <summary>
/// The name of this stream
/// </summary>
public string Name
{
get
{
if( StreamInfoDisposed ) // Null name in core signifies the core object is disposed
{
// The .Net Design Guidelines instruct us not to throw exceptions in property getters.
return "";
}
return core.streamName;
}
}
/***********************************************************************/
// Methods
/// <summary>
/// Opens a stream
/// </summary>
/// <returns>Stream object to manipulate data</returns>
public Stream GetStream()
{
return GetStream( defaultFileOpenMode, parentStorage.Root.OpenAccess );
}
/// <summary>
/// Opens a stream with the given open mode flags
/// </summary>
/// <param name="mode">Open mode flags</param>
/// <returns>Stream object to manipulate data</returns>
public Stream GetStream( FileMode mode )
{
return GetStream( mode, parentStorage.Root.OpenAccess );
}
/// <summary>
/// Opens a stream with the given open mode flags and access flags
/// </summary>
/// <param name="mode">Open mode flags</param>
/// <param name="access">File access flags</param>
/// <returns>Stream object to manipulate data</returns>
public Stream GetStream( FileMode mode, FileAccess access )
{
CheckDisposedStatus();
int grfMode = 0;
IStream openedIStream = null;
openFileAccess = access;
// becasue of the stream caching mechanism we must adjust FileAccess parameter.
// We want to open stream with the widest access posible, in case Package was open in ReadWrite
// we need to open stream in ReadWrite even if user explicitly asked us to do ReadOnly/WriteOnly.
// There is a possibility of a next request coming in as as ReadWrite request, and we would like to
// take advanatage of the cached stream by wrapping with appropriate access limitations.
if (parentStorage.Root.OpenAccess == FileAccess.ReadWrite)
{
// Generate the access flags from the access parameter
access = FileAccess.ReadWrite;
}
// Generate the access flags from the access parameter
SafeNativeCompoundFileMethods.UpdateModeFlagFromFileAccess( access, ref grfMode );
// Only SHARE_EXCLUSIVE for now, FileShare issue TBD
grfMode |= SafeNativeCompoundFileConstants.STGM_SHARE_EXCLUSIVE;
CheckAccessMode(grfMode);
// Act based on FileMode
switch(mode)
{
case FileMode.Append:
throw new ArgumentException(
SR.Get(SRID.FileModeUnsupported));
case FileMode.Create:
// Check to make sure root container is not read-only, and that
// we're not pointlessly trying to create a read-only stream.
CreateTimeReadOnlyCheck(openFileAccess);
// Close down any existing streams floating out there
if (null != core.exposedStream)
{
((Stream)(core.exposedStream)).Close();
}
core.exposedStream = null;
if( null != core.safeIStream )
{
// Close out existing stream
((IDisposable) core.safeIStream).Dispose();
core.safeIStream = null;
}
// Cleanup done, create new stream in its place
grfMode |= SafeNativeCompoundFileConstants.STGM_CREATE;
openedIStream = CreateStreamOnParentIStorage(
core.streamName,
grfMode );
break;
case FileMode.CreateNew:
throw new ArgumentException(
SR.Get(SRID.FileModeUnsupported));
case FileMode.Open:
// If we've got a stream, return a CFStream built from its clone
if( null != core.safeIStream )
{
return CFStreamOfClone(openFileAccess);
}
// Need to call Open API with NULL open flags
openedIStream = OpenStreamOnParentIStorage(
core.streamName,
grfMode );
break;
case FileMode.OpenOrCreate:
// If we've got a stream, return a CFStream built from its clone
if( null != core.safeIStream )
{
return CFStreamOfClone(openFileAccess);
}
// Skip creation attempt for read-only container or specifying
// read-only stream
if( FileAccess.Read != parentStorage.Root.OpenAccess &&
FileAccess.Read != openFileAccess )
{
// Try creating first. If it already exists then do an open. This
// seems ugly but this method involves the fewest number of
// managed/unmanaged transitions.
if( !parentStorage.Exists )
{
parentStorage.Create();
}
int nativeCallErrorCode =
parentStorage.SafeIStorage.CreateStream(
core.streamName,
grfMode,
0,
0,
out openedIStream );
if( SafeNativeCompoundFileConstants.S_OK != nativeCallErrorCode &&
SafeNativeCompoundFileConstants.STG_E_FILEALREADYEXISTS != nativeCallErrorCode )
{
throw new IOException(
SR.Get(SRID.UnableToCreateStream),
new COMException(
SR.Get(SRID.NamedAPIFailure, "IStorage.CreateStream"),
nativeCallErrorCode ));
}
// Parent storage has changed - invalidate all standing enuemrators
parentStorage.InvalidateEnumerators();
// else - proceed with open
}
if( null == openedIStream )
{
// If we make it here, it means the create stream call failed
// because of a STG_E_FILEALREADYEXISTS
// or container is read-only
openedIStream = OpenStreamOnParentIStorage(
core.streamName,
grfMode );
}
break;
case FileMode.Truncate:
throw new ArgumentException(
SR.Get(SRID.FileModeUnsupported));
default:
throw new ArgumentException(
SR.Get(SRID.FileModeInvalid));
}
core.safeIStream = openedIStream;
Stream returnStream =
BuildStreamOnUnderlyingIStream( core.safeIStream, openFileAccess, this );
core.exposedStream = returnStream;
return returnStream;
}
/***********************************************************************/
// Internal/Private functionality
/// <summary>
/// Creates a stream with all default parameters
/// </summary>
/// <returns>Stream object to manipulate data</returns>
internal Stream Create()
{
return Create( defaultFileCreateMode, parentStorage.Root.OpenAccess, defaultDataSpace );
}
/// <summary>
/// Creates a stream with the given create mode
/// </summary>
/// <param name="mode">Desired create mode</param>
/// <returns>Stream object to manipulate data</returns>
private Stream Create( FileMode mode )
{
return Create( mode, parentStorage.Root.OpenAccess, defaultDataSpace );
}
/// <summary>
/// Creates a stream encoded in the given data space
/// </summary>
/// <param name="dataSpaceLabel">Data space label</param>
/// <returns>Stream object to manipulate data</returns>
internal Stream Create( string dataSpaceLabel )
{
return Create( defaultFileCreateMode, parentStorage.Root.OpenAccess, dataSpaceLabel );
}
/// <summary>
/// Creates a stream with the given create and access flags
/// </summary>
/// <param name="mode">Desired create mode flag</param>
/// <param name="access">Access flags</param>
/// <returns>Stream object to manipulate data</returns>
private Stream Create( FileMode mode, FileAccess access )
{
return Create( mode, access, defaultDataSpace );
}
/// <summary>
/// Creates a stream with the given parameters
/// </summary>
/// <param name="mode">Creation mode</param>
/// <param name="access">Access mode</param>
/// <param name="dataSpace">Data space encoding</param>
/// <returns>Stream object to manipulate data</returns>
internal Stream Create( FileMode mode, FileAccess access, string dataSpace )
{
CheckDisposedStatus();
int grfMode = 0;
IStream createdSafeIStream = null;
DataSpaceManager dataSpaceManager = null;
// Check to make sure root container is not read-only, and that
// we're not pointlessly trying to create a read-only stream.
CreateTimeReadOnlyCheck( access );
// Check to see if the data space label is valid
if( null != dataSpace )
{
if( 0 == dataSpace.Length )
throw new ArgumentException(
SR.Get(SRID.DataSpaceLabelInvalidEmpty));
dataSpaceManager = parentStorage.Root.GetDataSpaceManager();
if( !dataSpaceManager.DataSpaceIsDefined( dataSpace ) )
throw new ArgumentException(
SR.Get(SRID.DataSpaceLabelUndefined));
}
openFileAccess = access;
// becasue of the stream caching mechanism we must adjust FileAccess parameter.
// We want to open stream with the widest access posible, in case Package was open in ReadWrite
// we need to open stream in ReadWrite even if user explicitly asked us to do ReadOnly/WriteOnly.
// There is a possibility of a next request coming in as as ReadWrite request, and we would like to
// take advanatage of the cached stream by wrapping with appropriate access limitations.
if (parentStorage.Root.OpenAccess == FileAccess.ReadWrite)
{
access = FileAccess.ReadWrite;
}
// Generate the access flags from the access parameter
SafeNativeCompoundFileMethods.UpdateModeFlagFromFileAccess( access, ref grfMode );
// Only SHARE_EXCLUSIVE for now, FileShare issue TBD
grfMode |= SafeNativeCompoundFileConstants.STGM_SHARE_EXCLUSIVE;
CheckAccessMode(grfMode);
// Act based on FileMode
switch(mode)
{
case FileMode.Create:
// Close down any existing streams floating out there
if (null != core.exposedStream)
{
((Stream)(core.exposedStream)).Close();
}
core.exposedStream = null;
if( null != core.safeIStream )
{
// Release reference
((IDisposable) core.safeIStream).Dispose();
core.safeIStream = null;
}
// Cleanup done, create new stream in its place.
grfMode |= SafeNativeCompoundFileConstants.STGM_CREATE;
createdSafeIStream = CreateStreamOnParentIStorage(
core.streamName,
grfMode );
break;
case FileMode.CreateNew:
// If we've created a CFStream, this fails because stream is already there.
if( null != core.safeIStream )
throw new IOException(
SR.Get(SRID.StreamAlreadyExist));
// Need to call Create API with NULL create flags
createdSafeIStream = CreateStreamOnParentIStorage(
core.streamName,
grfMode );
break;
case FileMode.Append: // None of these are valid in a Create
case FileMode.Open:
case FileMode.OpenOrCreate:
case FileMode.Truncate:
default:
throw new ArgumentException(
SR.Get(SRID.FileModeInvalid));
}
core.safeIStream = createdSafeIStream;
// At this point we passed all previous checks and got the underlying IStream.
// Set our data space label to the given label, and the stream to the retrieved stream.
core.dataSpaceLabel = dataSpace;
if( null != dataSpace )
{
dataSpaceManager.CreateDataSpaceMapping(
new CompoundFileStreamReference( parentStorage.FullNameInternal, core.streamName ),
core.dataSpaceLabel );
}
Stream returnStream =
BuildStreamOnUnderlyingIStream( core.safeIStream, openFileAccess, this );
_needToGetTransformInfo = false; // We created stream with the given dataspace setting
// so, there is no need to get the dataspace setting
core.exposedStream = returnStream;
return returnStream;
}
Stream BuildStreamOnUnderlyingIStream(
IStream underlyingIStream,
FileAccess access,
StreamInfo parent )
{
Stream rawStream = new CFStream( underlyingIStream, access, parent );
if( null == core.dataSpaceLabel )
{
// The stream is not transformed in any data space, add buffering and return
return new BufferedStream( rawStream );
}
else
{
// Pass raw stream to data space manager to get real stream
return parentStorage.Root.GetDataSpaceManager().CreateDataSpaceStream(
StreamReference, rawStream);
}
}
/// <summary>
/// A check against FileAccess.Read at create time. It should fail if
/// the root container is read-only, or if we're pointlessly trying
/// to create a read-only stream.
/// </summary>
void CreateTimeReadOnlyCheck( FileAccess access )
{
// Can't create a stream if the root container is read-only
if( FileAccess.Read == parentStorage.Root.OpenAccess )
throw new IOException(
SR.Get(SRID.CanNotCreateInReadOnly));
// Doesn't make sense to create a new stream just to make it read-only
if( access == FileAccess.Read )
throw new ArgumentException(
SR.Get(SRID.CanNotCreateAsReadOnly));
}
/// <summary>
/// Shortcut macro - calls the IStorage::CreateStream method on the parent
/// storage object.
/// </summary>
IStream CreateStreamOnParentIStorage(
string name,
int mode )
{
IStream createdStream = null;
int nativeCallErrorCode = 0;
if( !parentStorage.Exists )
{
parentStorage.Create();
}
nativeCallErrorCode = parentStorage.SafeIStorage.CreateStream(
name,
mode,
0,
0,
out createdStream );
if( SafeNativeCompoundFileConstants.STG_E_INVALIDFLAG == nativeCallErrorCode )
{
throw new ArgumentException(
SR.Get(SRID.StorageFlagsUnsupported));
}
else if ( SafeNativeCompoundFileConstants.S_OK != nativeCallErrorCode )
{
throw new IOException(
SR.Get(SRID.UnableToCreateStream),
new COMException(
SR.Get(SRID.NamedAPIFailure, "IStorage.CreateStream"),
nativeCallErrorCode ));
}
// Parent storage has changed - invalidate all standing enuemrators
parentStorage.InvalidateEnumerators();
return createdStream;
}
/// <summary>
/// Shortcut macro - calls the IStorage::OpenStream method on the parent
/// storage object.
/// </summary>
IStream OpenStreamOnParentIStorage(
string name,
int mode )
{
IStream openedStream = null;
int nativeCallErrorCode = 0;
nativeCallErrorCode = parentStorage.SafeIStorage.OpenStream(
name,
0,
mode,
0,
out openedStream );
if( SafeNativeCompoundFileConstants.S_OK != nativeCallErrorCode )
{
throw new IOException(
SR.Get(SRID.UnableToOpenStream),
new COMException(
SR.Get(SRID.NamedAPIFailure, "IStorage.OpenStream"),
nativeCallErrorCode ));
}
return openedStream;
}
/// <summary>
/// Deletes the stream specified by this StreamInfo
/// </summary>
internal void Delete()
{
CheckDisposedStatus();
if( InternalExists() )
{
if( null != core.safeIStream )
{
// Close out existing stream
((IDisposable) core.safeIStream).Dispose();
core.safeIStream = null;
}
parentStorage.DestroyElement( core.streamName );
// Parent storage has changed - invalidate all standing enuemrators
parentStorage.InvalidateEnumerators();
}
else
{
// If a FileInfo is told to delete a file that does not
// exist, nothing happens. We follow that example here.
}
}
/// <summary>
/// It is valid to have a StreamInfo class that points to a stream
/// that does not (yet) exist. However, it is impossible to perform
/// operations on a stream that does not exst, so the methods that
/// require an existing stream need to be able to check if the stream
/// exists before trying to perform its operations.
/// </summary>
/// <returns>Whether "this" stream exists</returns>
internal bool InternalExists()
{
// If we have a stream, it's pretty obvious that we exist.
if( null != core.safeIStream )
return true;
// If parent storage does not exist, we can't possibly exist either
if( !parentStorage.Exists )
return false;
// At this point we know the parent storage exists, but we don't know
// if we do. Try to open the stream.
return SafeNativeCompoundFileConstants.S_OK == parentStorage.SafeIStorage.OpenStream(
core.streamName,
0,
SafeNativeCompoundFileConstants.STGM_READ | SafeNativeCompoundFileConstants.STGM_SHARE_EXCLUSIVE,
0,
out core.safeIStream );
}
/// <summary>
/// Most of the time internal methods that want to do an internal check
/// to see if a stream exists is only interested in proceeding if it does.
/// If it doesn't, abort with an exception. This implements the little
/// shortcut.
/// </summary>
void VerifyExists()
{
if( !InternalExists() )
{
throw new IOException(
SR.Get(SRID.StreamNotExist));
}
return;
}
// This fixes bug# 4947, a degenerate case of bug #5563
private Stream CFStreamOfClone( FileAccess access )
{
long dummy = 0;
IStream cloneStream = null;
core.safeIStream.Clone( out cloneStream );
cloneStream.Seek( 0, SafeNativeCompoundFileConstants.STREAM_SEEK_SET, out dummy );
Stream returnStream =
BuildStreamOnUnderlyingIStream( cloneStream, access, this );
core.exposedStream = returnStream;
return returnStream;
}
// Check whether this StreamInfo object is still valid. If not, thrown an
// ObjectDisposedException.
internal void CheckDisposedStatus()
{
// Check to see if we're still valid.
if( StreamInfoDisposed ) // Null name in core signifies the core object is disposed
throw new ObjectDisposedException(null, SR.Get(SRID.StreamInfoDisposed));
}
// Check whether this StreamInfo object is still valid. Return result.
internal bool StreamInfoDisposed
{
get
{
// Check to see if we're still valid.
// Null name in core signifies the core object is disposed.
// Also check the parent storage.
return (( null == core.streamName ) || parentStorage.StorageDisposed);
}
}
// If we opened the IStream but haven't publicly exposed any Streams yet (i.e. InternalExists),
// check to make sure the access modes match.
internal void CheckAccessMode(int grfMode)
{
// Do we have an IStream?
if( null != core.safeIStream )
{
// Have we exposed it publicly yet?
if( null == core.exposedStream )
{
System.Runtime.InteropServices.ComTypes.STATSTG mySTATs;
core.safeIStream.Stat( out mySTATs, SafeNativeCompoundFileConstants.STATFLAG_NONAME );
// Do the modes match?
if( grfMode != mySTATs.grfMode )
{
// Modes don't match, close out existing stream.
((IDisposable) core.safeIStream).Dispose();
core.safeIStream = null;
}
}
}
}
internal CompoundFileStreamReference StreamReference
{
get
{
return _streamReference;
}
}
// Inspect the transforms applied this stream and retreive the compression and
// RM encryption options
private void EnsureTransformInformation()
{
if (_needToGetTransformInfo && InternalExists())
{
_encryptionOption = EncryptionOption.None;
_compressionOption = CompressionOption.NotCompressed;
//If the StreamInfo exists we go on to check if correct transform has been
//applied to the Stream
DataSpaceManager dsm = parentStorage.Root.GetDataSpaceManager();
List<IDataTransform> transforms = dsm.GetTransformsForStreamInfo(this);
foreach (IDataTransform dataTransform in transforms)
{
string id = dataTransform.TransformIdentifier as string;
if (id != null)
{
id = id.ToUpperInvariant();
if (String.CompareOrdinal(id,
RightsManagementEncryptionTransform.ClassTransformIdentifier.ToUpperInvariant()) == 0
&&
(dataTransform as RightsManagementEncryptionTransform) != null)
{
_encryptionOption = EncryptionOption.RightsManagement;
}
else if (String.CompareOrdinal(id,
CompressionTransform.ClassTransformIdentifier.ToUpperInvariant()) == 0
&&
(dataTransform as CompressionTransform) != null)
{
// We don't persist the compression level used during compression process
// When we access the stream, all we can determine is whether it is compressed or not
// In all our scenarios, the level we use is Level 9 which is equivalent to Maximum
_compressionOption = CompressionOption.Maximum;
}
}
}
_needToGetTransformInfo = false;
}
}
}
}
|