//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 1993.
//
//  File:       propstg.hxx
//
//  Contents:   Class that directly implements IPropertyStorage
//
//  Classes:    CCoTaskAllocator
//              CPropertyStorage
//              CEnumSTATPROPSTG
//
//  Functions:
//
//
//
//  History:    1-Mar-95   BillMo      Created.
//              25-Jan-96  MikeHill    Added _fmtidSection.
//              22-May-96  MikeHill    Added _dwOSVersion to CPropertyStorage.
//              06-Jun-96  MikeHill    Added support for input validation.
//              18-Aug-96  MikeHill    - Added CDocFilePropertyStorage.
//              07-Feb-97  Danl        - Removed CDocFilePropertyStorage.
//              18-Aug-98  MikeHill    Probe stream in IsWriteable.
//
//  Notes:
//
//--------------------------------------------------------------------------


//+-------------------------------------------------------------------------
//
//  Class:      CCoTaskAllocator
//
//  Purpose:    Class used by PrQueryProperties to allocate memory.
//
//--------------------------------------------------------------------------

class CCoTaskAllocator : public PMemoryAllocator
{
public:
    virtual void *Allocate(ULONG cbSize);
    virtual void Free(void *pv);

};


extern const OLECHAR g_oszPropertyContentsStreamName[];


//+-------------------------------------------------------------------------
//
//  Class:      CPropertyStorage
//
//  Purpose:    Implements IPropertyStorage.
//
//  Notes:      This class uses the functionality provided by
//              PrCreatePropertySet, PrSetProperties, PrQueryProperties etc
//              to manipulate the property set stream.
//
//--------------------------------------------------------------------------

#define PROPERTYSTORAGE_SIG LONGSIG('P','R','P','S')
#define PROPERTYSTORAGE_SIGDEL LONGSIG('P','R','P','s')
#define PROPERTYSTORAGE_SIGZOMBIE LONGSIG('P','R','P','z')
#define ENUMSTATPROPSTG_SIG LONGSIG('E','P','S','S')
#define ENUMSTATPROPSTG_SIGDEL LONGSIG('E','P','S','s')

// Prototype for test function
WORD GetFormatVersion( IPropertyStorage *ppropstg );

class CPropertyStorage : public IPropertyStorage
#if DBG
                         , public IStorageTest
#endif
{
    friend WORD GetFormatVersion(IPropertyStorage *ppropstg);  // Used for test only

    //  ------------
    //  Constructors
    //  ------------

public:

    // The destructor must be virtual so that derivative destructors
    // will execute.

    virtual ~CPropertyStorage();

    CPropertyStorage(MAPPED_STREAM_OPTS fMSOpts)
    {
        _fInitCriticalSection = FALSE;
        _fMSOpts = fMSOpts;
        Initialize();

        #ifndef _MAC
            __try
            {
                InitializeCriticalSection( &_CriticalSection );
                _fInitCriticalSection = TRUE;
            }
            __except( EXCEPTION_EXECUTE_HANDLER )
            {
            }
        #endif
    }

    //  --------
    //  IUnknown
    //  --------
public:

    STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject);

    STDMETHOD_(ULONG, AddRef)(void);

    STDMETHOD_(ULONG, Release)(void);

    //  ----------------
    //  IPropertyStorage
    //  ----------------
public:

    STDMETHOD(ReadMultiple)(
                ULONG                   cpspec,
                const PROPSPEC          rgpspec[],
                PROPVARIANT             rgpropvar[]);

    STDMETHOD(WriteMultiple)(
                ULONG                   cpspec,
                const PROPSPEC          rgpspec[],
                const PROPVARIANT       rgpropvar[],
                PROPID                  propidNameFirst);

    STDMETHOD(DeleteMultiple)(
                ULONG                   cpspec,
                const PROPSPEC          rgpspec[]);

    STDMETHOD(ReadPropertyNames)(
                ULONG                   cpropid,
                const PROPID            rgpropid[],
                LPOLESTR                rglpwstrName[]);

    STDMETHOD(WritePropertyNames)(
                ULONG                   cpropid,
                const PROPID            rgpropid[],
                const LPOLESTR          rglpwstrName[]);

    STDMETHOD(DeletePropertyNames)(
                ULONG                   cpropid,
                const PROPID            rgpropid[]);

    STDMETHOD(Commit)(DWORD             grfCommitFlags);

    STDMETHOD(Revert)();

    STDMETHOD(Enum)(IEnumSTATPROPSTG ** ppenum);

    STDMETHOD(SetTimes)(
                FILETIME const *        pctime,
                FILETIME const *        patime,
                FILETIME const *        pmtime);

    STDMETHOD(SetClass)(REFCLSID        clsid);

    STDMETHOD(Stat)(STATPROPSETSTG *    pstatpsstg);

    //  ------------
    //  IStorageTest
    //  ------------
public:

#if DBG
    STDMETHOD(UseNTFS4Streams)( BOOL fUseNTFS4Streams );
    STDMETHOD(GetFormatVersion)(WORD *pw);
    STDMETHOD(SimulateLowMemory)( BOOL fSimulate );
    STDMETHOD(GetLockCount)();
    STDMETHOD(IsDirty)();
#endif


    //  -----------------------------
    //  Exposed non-interface methods
    //  -----------------------------

public:


    HRESULT Create(
        IStream         *stm,
        REFFMTID        rfmtid,
        const CLSID     *pclsid,
        DWORD           grfFlags,
        DWORD           grfMode );

    HRESULT Create(
        IStorage        *pstg,
        REFFMTID        rfmtid,
        const CLSID     *pclsid,
        DWORD           grfFlags,
        DWORD           grfMode );

    HRESULT Open(
        IStorage *pstg,
        REFFMTID  rfmtid,
        DWORD     grfFlags,
        DWORD     grfMode );

    HRESULT Open(
        IStream *pstm,
        REFFMTID  rfmtid,
        DWORD     grfFlags,
        DWORD     grfMode,
        BOOL      fDelete );

    // used by implementation helper classes
    inline NTPROP  GetNtPropSetHandle(void) { return _np; }

    //  ----------------
    //  Internal Methods
    //  ----------------

private:

    void        Initialize();
    HRESULT     InitializeOnCreateOrOpen(
                    DWORD grfFlags,
                    DWORD grfMode,
                    REFFMTID rfmtid,
                    BOOL fCreate );

    VOID        CleanupOpenedObjects(
                    PROPVARIANT         avar[],
                    INDIRECTPROPERTY *  pip,
                    ULONG               cpspec,
                    ULONG               iFailIndex);

    enum EInitializePropertyStream
    {
        OPEN_PROPSTREAM,
        CREATE_PROPSTREAM,
        DELETE_PROPSTREAM
    };

    HRESULT     InitializePropertyStream(
                    const GUID       *  pguid,
                    GUID const *        pclsid,
                    EInitializePropertyStream CreateOpenDelete );

    HRESULT     _WriteMultiple(
                    ULONG               cpspec,
                    const PROPSPEC      rgpspec[],
                    const PROPVARIANT   rgpropvar[],
                    PROPID              propidNameFirst);

    HRESULT     _WritePropertyNames(
                    ULONG                   cpropid,
                    const PROPID            rgpropid[],
                    const LPOLESTR          rglpwstrName[]);

    BOOL        ProbeStreamToDetermineIfWriteable();

    virtual HRESULT CreateMappedStream( );
    virtual void    DeleteMappedStream( );

    VOID        Lock();
    VOID        Unlock();
    VOID        AssertLocked();


    inline HRESULT  Validate();
    inline HRESULT  ValidateRef();

    inline HRESULT  ValidateRGPROPSPEC( ULONG cpspec, const PROPSPEC rgpropspec[] );
    inline HRESULT  ValidateRGPROPID( ULONG cpropid, const PROPID rgpropid[] );
    HRESULT         ValidateVTs( ULONG cprops, const PROPVARIANT rgpropvar[] );

    enum EIsWriteable
    {
        PROBE_IF_NECESSARY,
        DO_NOT_PROBE
    };

    inline BOOL     IsNonSimple();
    inline BOOL     IsSimple();
    inline BOOL     IsWriteable( EIsWriteable ProbeEnum = PROBE_IF_NECESSARY );
    inline BOOL     IsReadable();
    inline BOOL     IsReverted();

    inline DWORD    GetChildCreateMode();
    inline DWORD    GetChildOpenMode();

    //  -------------
    //  Internal Data
    //  -------------

private:

    ULONG               _ulSig;
    LONG                _cRefs;
    IStorage *          _pstgPropSet;
    IStream  *          _pstmPropSet;
    NTPROP              _np;
    IMappedStream *     _ms;
    MAPPED_STREAM_OPTS  _fMSOpts;

#ifndef _MAC
    BOOL             _fInitCriticalSection;
    CRITICAL_SECTION _CriticalSection;
#endif

#if DBG
    LONG                _cLocks;
#endif

    // We need to remember if the property set is the second section
    // of the DocumentSummaryInformation property set used by Office.  This
    // is the only case where we support a multiple-section property set, and
    // requires special handling in ::Revert().

    BOOL            _fUserDefinedProperties:1;

    // If _grfMode is zero, maybe it's valid and really zero, or maybe
    // we're dealing with a stream that doesn't return the grfMode in the
    // Stat (such as is the case with CreateStreamFromHGlobal).  So if we
    // get a zero _grfMode, we probe the stream to determine if it's writeable.
    // This flag ensures we only do it once.

    BOOL            _fExplicitelyProbedForWriteAccess:1;


    USHORT          _usCodePage;  // Ansi Codepage or Mac Script
                                  //    (disambiguate with _dwOSVersion)
    ULONG           _dwOSVersion; // Shows the OS Kind, major/minor version


    DWORD           _grfFlags;  // PROPSETFLAG_NONSIMPLE and PROPSETFLAG_ANSI
    DWORD           _grfMode;   // STGM_ flags

    // The following is a PMemoryAllocator for the Rtl property
    // set routines.  This instantiation was formerly a file-global
    // in propstg.cxx.  However, on the Mac, the object was not
    // being instantiated.

    CCoTaskAllocator _cCoTaskAllocator;

};

//+-------------------------------------------------------------------
//
//  Member:     CPropertyStorage::Validate
//
//  Synopsis:   S_OK if signature valid and not zombie.
//
//  Notes:      If the Revert calls fails due to low memory, then
//              the object becomes a zombie.
//
//--------------------------------------------------------------------

inline HRESULT CPropertyStorage::Validate()
{
    if(!_fInitCriticalSection)
        return E_OUTOFMEMORY;

    if (_ulSig == PROPERTYSTORAGE_SIG)
        return S_OK;
    else
    if (_ulSig == PROPERTYSTORAGE_SIGZOMBIE)
        return STG_E_INSUFFICIENTMEMORY;
    else
        return STG_E_INVALIDHANDLE;
}

//+-------------------------------------------------------------------
//
//  Member:     CPropertyStorage::ValidateRef
//
//  Synopsis:   S_OK if signature valid.
//
//--------------------------------------------------------------------

inline HRESULT CPropertyStorage::ValidateRef()
{
    if (_ulSig == PROPERTYSTORAGE_SIG || _ulSig == PROPERTYSTORAGE_SIGZOMBIE)
        return(S_OK);
    else
        return(STG_E_INVALIDHANDLE);

}


//+-------------------------------------------------------------------
//
//  Member:     CPropertyStorage::AssertLocked
//
//  Synopsis:   Asserts if _cLocks is <= zero.
//
//--------------------------------------------------------------------

inline VOID CPropertyStorage::AssertLocked()
{
    DfpAssert( 0 < _cLocks );
}

//+-------------------------------------------------------------------
//
//  Member:     CPropertyStorage::ValidateRGPROPSPEC
//
//  Synopsis:   S_OK if PROPSPEC[] is valid
//              E_INVALIDARG otherwise.
//
//--------------------------------------------------------------------

inline HRESULT CPropertyStorage::ValidateRGPROPSPEC( ULONG cpspec,
                                                     const PROPSPEC rgpropspec[] )
{
    HRESULT hr = E_INVALIDARG;

    // Validate the array itself.

    VDATESIZEREADPTRIN_LABEL(rgpropspec, cpspec * sizeof(PROPSPEC), errRet, hr);

    // Validate the elements of the array.

    for( ; cpspec > 0; cpspec-- )
    {
        // Is this an LPWSTR?
        if( PRSPEC_LPWSTR == rgpropspec[cpspec-1].ulKind )
        {
            // We better at least be able to read the first
            // character.
            VDATEREADPTRIN_LABEL(rgpropspec[cpspec-1].lpwstr, WCHAR, errRet, hr);
        }

        // Otherwise, this better be a PROPID.
        else if( PRSPEC_PROPID != rgpropspec[cpspec-1].ulKind )
        {
            goto errRet;
        }
    }

    hr = S_OK;

    //  ----
    //  Exit
    //  ----

errRet:

    return( hr );
}

//+-------------------------------------------------------------------
//
//  Member:     CPropertyStorage::ValidateRGPROPID
//
//  Synopsis:   S_OK if RGPROPID[] is valid for Read.
//              E_INVALIDARG otherwise.
//
//--------------------------------------------------------------------

inline HRESULT CPropertyStorage::ValidateRGPROPID( ULONG cpropid,
                                                   const PROPID rgpropid[] )
{
    HRESULT hr;
    VDATESIZEREADPTRIN_LABEL( rgpropid, cpropid * sizeof(PROPID), errRet, hr );
    hr = S_OK;

errRet:

    return( hr );
}

//+-------------------------------------------------------------------
//
//  Member:     CPropertyStorage::IsNonSimple
//
//  Synopsis:   true if non-simple
//
//--------------------------------------------------------------------

inline BOOL CPropertyStorage::IsNonSimple()
{
    AssertLocked();
    return (_grfFlags & PROPSETFLAG_NONSIMPLE) != 0;
}

//+-------------------------------------------------------------------
//
//  Member:     CPropertyStorage::IsSimple
//
//  Synopsis:   true if simple
//
//--------------------------------------------------------------------

inline BOOL CPropertyStorage::IsSimple()
{
    AssertLocked();
    return !IsNonSimple();
}

//+-------------------------------------------------------------------
//
//  Member:     CPropertyStorage::IsWriteable
//
//  Synopsis:   Returns TRUE if writeable, FALSE otherwise.
//
//              If _grfMode is zero, we may actually have a writeable
//              stream, but _grfMode wasn't returned in IStream::Stat.
//              So unless explicitely told not to, we'll probe for
//              write access.  If explicitely told not to
//              (by passing DO_NOT_PROBE), we'll assume that it's
//              read-only.
//
//--------------------------------------------------------------------

inline BOOL CPropertyStorage::IsWriteable( EIsWriteable ProbeEnum )
{
    AssertLocked();

    if( ::GrfModeIsWriteable(_grfMode) )
        return( TRUE );

    if( 0 != _grfMode || _fExplicitelyProbedForWriteAccess )
        // It's explicitely not writeable
        return( FALSE );
    else
    {
        // It's either STGM_READ|STGM_DENYNONE, or _grfMode
        // wasn't set.

        if( PROBE_IF_NECESSARY == ProbeEnum )
            return( ProbeStreamToDetermineIfWriteable() );
        else
            return( FALSE );    // Play it safe and assume read-only.
    }

}

//+-------------------------------------------------------------------
//
//  Member:     CPropertyStorage::IsReadable
//
//  Synopsis:   S_OK if readable otherwise STG_E_ACCESSDENIED
//
//--------------------------------------------------------------------

inline BOOL CPropertyStorage::IsReadable()
{
    AssertLocked();
    return( ::GrfModeIsReadable(_grfMode) );
}

//+-------------------------------------------------------------------
//
//  Member:     CPropertyStorage::IsReverted
//
//  Synopsis:   TRUE if reverted, FALSE otherwise.
//
//--------------------------------------------------------------------

inline BOOL CPropertyStorage::IsReverted()
{
    IUnknown *punk = NULL;
    HRESULT hr = S_OK;
    BOOL fReverted = FALSE;

    AssertLocked();
    
    if( (NULL == _pstgPropSet) && (NULL == _pstmPropSet) )
        return( TRUE );

    hr = (IsNonSimple() ? (IUnknown*)_pstgPropSet : (IUnknown*)_pstmPropSet) ->
        QueryInterface(IID_IUnknown, (void**)&punk);

    // Note:  On older Mac implementations, memory-based streams will
    // return E_NOINTERFACE here, due to a bug in the QueryInterface
    // implementation.

    if( STG_E_REVERTED == hr )
        fReverted = TRUE;
    else
        fReverted = FALSE;;

    if( SUCCEEDED(hr) )
        punk->Release();

    return( fReverted );
}



inline DWORD
CPropertyStorage::GetChildCreateMode()
{
    DWORD dwMode;
    AssertLocked();

    dwMode = _grfMode & ~(STGM_SHARE_MASK|STGM_TRANSACTED);
    dwMode |= STGM_SHARE_EXCLUSIVE | STGM_CREATE;
    return( dwMode );
}

inline DWORD
CPropertyStorage::GetChildOpenMode()
{
    AssertLocked();
    return( GetChildCreateMode() & ~STGM_CREATE );
}



//+-------------------------------------------------------------------------
//
//  Class:      IStatArray
//
//--------------------------------------------------------------------------

interface IStatArray  : public IUnknown
{

public:

    virtual NTSTATUS NextAt( ULONG ipropNext, STATPROPSTG *pspsDest, ULONG *pceltFetched )=0;

};


//+-------------------------------------------------------------------------
//
//  Class:      CStatArray
//
//  Purpose:    Class to allow sharing of enumeration STATPROPSTG state.
//
//  Interface:  CStatArray -- Enumerate entire NTPROP np.
//              NextAt -- Perform an OLE-type Next operation starting at
//                        specified offset.
//              AddRef -- for sharing of this by CEnumSTATPROPSTG
//              Release -- ditto
//
//  Notes:      Each IEnumSTATPROPSTG instance has a reference to a
//              CStatArray.  When IEnumSTATPROPSTG::Clone is called, a
//              new reference to the extant CStatArray is used: no copying.
//
//              The CEnumSTATPROPSTG has a cursor into the CStatArray.
//
//--------------------------------------------------------------------------

class CStatArray : public IStatArray
{
public:

    CStatArray( NTPROP np, HRESULT *phr );

private:

    ~CStatArray();

public:

    STDMETHODIMP QueryInterface ( REFIID riid, void **ppvObject);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

public:

    NTSTATUS NextAt(ULONG ipropNext, STATPROPSTG *pspsDest, ULONG *pceltFetched);

private:

    LONG                _cRefs;
    STATPROPSTG *       _psps;
    ULONG               _cpropActual;
};

//+-------------------------------------------------------------------------
//
//  Class:      CEnumSTATPROPSTG
//
//  Purpose:    Implement IEnumSTATPROPSTG
//
//  Notes:      Just holds a reference to a CStatArray that contains
//              a static copy of the enumeration when the original
//              Enum call was made.  This object contains the cursor
//              into the CStatArray.
//
//--------------------------------------------------------------------------

NTSTATUS CopySTATPROPSTG( ULONG celt, STATPROPSTG * pspsDest, const STATPROPSTG * pspsSrc);

class CEnumSTATPROPSTG : public IEnumSTATPROPSTG
{
	//	------------
	//	Construction
	//	------------
public:

	CEnumSTATPROPSTG( IStatArray *psa ): _ulSig(ENUMSTATPROPSTG_SIG),
										 _ipropNext(0),
										 _psa(psa),
										 _cRefs(1)
	{
		_psa->AddRef();
	}
	
	CEnumSTATPROPSTG(const CEnumSTATPROPSTG &other );

	//	-----------
	//	Destruction
	//	-----------
private:

	~CEnumSTATPROPSTG();

	//	----------------
	//	IUnknown Methods
	//	----------------

public:

	STDMETHOD(QueryInterface)( REFIID riid, void **ppvObject);
	STDMETHOD_(ULONG, AddRef)(void);
	STDMETHOD_(ULONG, Release)(void);

	//	-------------
	//	IEnum Methods
	//	-------------

public:

	STDMETHOD(Skip)(ULONG celt);
	STDMETHOD(Reset)();
	STDMETHOD(Clone)(IEnumSTATPROPSTG ** ppenum);

	// We don't need RemoteNext.
	STDMETHOD(Next)(ULONG celt, STATPROPSTG * rgelt, ULONG * pceltFetched);

	//	----------------
	//	Internal Methods
	//	----------------

private:

    HRESULT	Validate();


	//	--------------
	//	Internal State
	//	--------------

private:


        ULONG               _ulSig;
        LONG                _cRefs;

        IStatArray *        _psa;
        ULONG               _ipropNext;
};

//+-------------------------------------------------------------------
//
//  Member:     CEnumSTATPROPSTG::Validate
//
//  Synopsis:   S_OK if signature is valid, otherwise error
//
//--------------------------------------------------------------------

inline HRESULT CEnumSTATPROPSTG::Validate()
{
    return _ulSig == ENUMSTATPROPSTG_SIG ? S_OK : STG_E_INVALIDHANDLE;
}




//+-------------------------------------------------------------------
//
//  Member:     CheckFlagsOnCreateOrOpen
//
//  Synopsis:
//
//  Notes:
//
//
//--------------------------------------------------------------------

inline HRESULT CheckFlagsOnCreateOrOpen(
    BOOL    fCreate,
    DWORD   grfMode
    )
{
    // Check for any mode disallowed flags
    if (grfMode & ( (fCreate ? 0 : STGM_CREATE)
                    |
                    STGM_PRIORITY | STGM_CONVERT
                    |
                    STGM_DELETEONRELEASE ))
    {
        return (STG_E_INVALIDFLAG);
    }

    // Ensure that we'll have read/write access to any storage/stream we create.
    // If grfMode is zero, assume that it's uninitialized and that we'll have
    // read/write access.  If we don't, the client will still get an error
    // at some point, just not in the open path.

    if( fCreate
        &&
        (grfMode & STGM_READWRITE) != STGM_READWRITE
        &&
        0 != grfMode )
    {
        return (STG_E_INVALIDFLAG);
    }

    return(S_OK);
}