|
|
//+--------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1993
//
// File: propstm.cxx
//
// Contents: property set value extraction code
//
// The OLE 2.0 Appendix B property set specifies multiple sections in the
// property stream specification. Multiple sections were intended to allow
// the schema associated with the property set to evolve over a period of
// time, but there is no reason that new PROPIDs cannot serve the same
// purpose. The current implementation of the property stream is limited to
// one section, except for the Office DocumentSummaryInformation property
// set's specific use of a second section. Other property sets with multiple
// sections can only be accessed in read-only mode, and then only for the
// first property section. The current implementation of property set stream
// is built around a class called CPropertySetStream. The various details of
// the OLE property spec is confined to this class. This class encapsulates a
// stream implementation (CMappedStream). This is different from other
// stream implementations in that the fundamental mechanism provided
// for acessing the contents is Map/Unmap rather than Read/Write.
//
//---------------------------------------------------------------------------
#include "pch.cxx"
#ifdef _UNIX
#define qsort ref_qsort
#include "qsort.h"
#endif
#define Dbg DEBTRACE_PROPERTY
#define szX "x" // allows radix change for offsets & sizes
//#define szX "d" // allows radix change for offsets & sizes
#ifndef newk
#define newk(Tag, pCounter) new
#endif
#ifndef IsDwordAligned
#define IsDwordAligned(p) (((ULONG) (p) & (sizeof(ULONG) - 1)) == 0)
#endif
#ifndef DwordRemain
#define DwordRemain(cb) \
((sizeof(ULONG) - ((cb) % sizeof(ULONG))) % sizeof(ULONG)) #endif
// Macro to create the OS Version field of the
// property set header.
#define MAKEPSVER(oskind, major, minor) \
(((oskind) << 16) | ((minor) << 8) | (major))
// we specify a new 'OS' type for the reference implementation
#define CURRENT_OSKIND OSKIND_REF
// reference implementation starts at version 1.00
#define CURRENT_OSVER 0x01
#define PROPSETVER_CURRENT \
MAKEPSVER(CURRENT_OSKIND, CURRENT_OSVER >> 8, CURRENT_OSVER & 0xff) #define PROPSETVER_WIN310 MAKEPSVER(OSKIND_WINDOWS, 3, 10)
#define PROPSETVER_WIN333 MAKEPSVER(OSKIND_WIN32, 3, 0x33)
extern GUID guidSummary; extern GUID guidDocumentSummary; extern GUID guidDocumentSummarySection2;
#define CP_DEFAULT_NONUNICODE 1252 // ANSI Latin1 (US, Western Europe)
extern "C" UNICODECALLOUTS UnicodeCallouts; #define CP_CREATEDEFAULT(state) (*UnicodeCallouts.pfnGetACP)()
#if DBGPROP
#define StatusCorruption(pstatus, szReason) \
_StatusCorruption(szReason " ", pstatus) #else
#define StatusCorruption(pstatus, szReason) \
_StatusCorruption(pstatus) #endif
VOID RtlpConvertToUnicode( IN CHAR const *pch, IN ULONG cb, IN USHORT CodePage, OUT WCHAR **ppwc, OUT ULONG *pcb, OUT NTSTATUS *pstatus);
#if DBGPROP
#define CB_VALUEDISPLAY 8 // Number of bytes to display
#define CB_VALUESTRING (CB_VALUEDISPLAY * 3 + 3) // "xx xx xx xx...\0"
char * ValueToString(SERIALIZEDPROPERTYVALUE const *pprop, ULONG cbprop, char buf[]) { char *p = buf; BYTE const *pb = pprop->rgb; BOOLEAN fOverflow = FALSE; static char szDots[] = "...";
if (cbprop >= FIELD_OFFSET(SERIALIZEDPROPERTYVALUE, rgb)) { cbprop -= FIELD_OFFSET(SERIALIZEDPROPERTYVALUE, rgb); if (cbprop > CB_VALUEDISPLAY) { cbprop = CB_VALUEDISPLAY; fOverflow = TRUE; } while (cbprop-- > 0) { if (p != buf) { *p++ = ' '; } p += PropSprintfA( p, "%02.2x", *pb++ ); } } *p = '\0'; PROPASSERT(p - buf + sizeof(szDots) <= CB_VALUESTRING); if (fOverflow) { strcpy(p, szDots); } return(buf); }
#define CB_VARIANT_TO_STRING 35
char * VariantToString(PROPVARIANT const &var, char buf[], ULONG cbprop) { char *p = buf;
PROPASSERT( cbprop >= CB_VARIANT_TO_STRING );
// Add the VT to the output buffer.
p += PropSprintfA( p, "vt=%04.4x", var.vt ); p += PropSprintfA( p, ", val=(%08.8x, %08.8x)", var.uhVal.QuadPart ); *p = '\0'; PROPASSERT( (p - buf) == CB_VARIANT_TO_STRING); return(buf); }
#endif
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_MapOffsetToAddress, private
//
// Synopsis: maps an offset to an address
//
// Arguments: [Offset] -- the offset in the section
//
// Returns: ptr to the offset mapped
//+--------------------------------------------------------------------------
inline VOID * CPropertySetStream::_MapOffsetToAddress(ULONG Offset) const { PROPASSERT(_cSection != 0);
return(Add2Ptr(_GetSectionHeader(), Offset)); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_DictionaryEntryLength
//
// Synopsis: Calculate the length of an entry in the
// dictionary. This is non-trivial because
// it is codepage-dependent.
//
// Arguments: [pent] -- pointer to a dictionary entry.
//
//
// Returns: The entry's length.
//+--------------------------------------------------------------------------
inline ULONG CPropertySetStream::_DictionaryEntryLength( IN ENTRY UNALIGNED const * pentArg ) const { #if i386 == 0 // copy into an aligned structure
ENTRY ent; // allocate on stack -> faster
ENTRY *pent=&ent; memcpy(pent, pentArg, sizeof(ENTRY)); #else
ENTRY UNALIGNED const *pent = pentArg; #endif
// If this is a Unicode property set, it should be DWORD-aligned.
PROPASSERT( _CodePage != CP_WINUNICODE || IsDwordAligned( (ULONG) pent ));
// The size consists of the length of the
// PROPID and character count ...
ULONG ulSize = CB_ENTRY;
// Plus the length of the string ...
ulSize += PropByteSwap( pent->cch ) * ( _CodePage == CP_WINUNICODE ? sizeof( WCHAR ) : sizeof( CHAR ) );
// Plus, possibly, padding to make the entry DWORD-aligned
// (for Unicode property sets).
if( _CodePage == CP_WINUNICODE ) { ulSize = DwordAlign( ulSize ); }
return( ulSize ); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_NextDictionaryEntry
//
// Synopsis: Given a pointer to an entry in the dictionary,
// create a pointer to the next entry.
//
// Arguments: [pent] -- pointer to a dictionary entry.
//
// Returns: Pointer to the next entry. If the input
// points to the last entry in the dictionary,
// then return a pointer to just beyond the
// end of the dictionary.
//+--------------------------------------------------------------------------
inline ENTRY UNALIGNED * CPropertySetStream::_NextDictionaryEntry( IN ENTRY UNALIGNED const * pent ) const {
return (ENTRY UNALIGNED *) Add2Ptr( pent, _DictionaryEntryLength( pent ));
}
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_SignalCorruption
//
// Synopsis: possibly PROPASSERT and return data corrupt error
//
// Arguments: [szReason] -- string explanation (DBGPROP only)
// [pstatus] -- NTSTATUS code.
//
// Returns: None
//+--------------------------------------------------------------------------
VOID CPropertySetStream::_StatusCorruption( #if DBGPROP
char *szReason, #endif
OUT NTSTATUS *pstatus ) const { #if DBGPROP
DebugTrace(0, DEBTRACE_ERROR, ( "_StatusCorruption(%s, psstm=%lx, mapstm=%lx, %s, flags=%x)\n", szReason, this, KERNELSELECT(&_mstm, _pmstm), KERNELSELECT("Kernel", _MSTM(IsNtMappedStream)()? "Nt" : "DocFile"), _Flags));
PROPASSERTMSG(szReason, FALSE); DebugTrace(0, DEBTRACE_WARN, ( "_StatusCorruption(%s, psstm=%lx, mapstm=%lx, %s, flags=%x)\n", szReason, this, KERNELSELECT(&_mstm, _pmstm), KERNELSELECT("Kernel", _MSTM(IsNtMappedStream)()? "Nt" : "DocFile"), _Flags)); if (DebugLevel & DEBTRACE_WARN) { PROPASSERTMSG(szReason, FALSE); }
#endif // DBGPROP
*pstatus = STATUS_INTERNAL_DB_CORRUPTION; return; }
//+--------------------------------------------------------------------------
// Function: _PropMoveMemory
//
// Synopsis: call DebugTrace and RtlMoveMemory
//
// Arguments: [pszReason] -- string explanation (Debug only)
// [pvSection] -- base of section (Debug only)
// [pvDst] -- destination
// [pvSrc] -- source
// [cbMove] -- byte count to move
//
// Returns: None
//+--------------------------------------------------------------------------
#if DBGPROP
#define PropMoveMemory(pszReason, pvSection, pvDst, pvSrc, cbMove) \
_PropMoveMemory(pszReason, pvSection, pvDst, pvSrc, cbMove) #else
#define PropMoveMemory(pszReason, pvSection, pvDst, pvSrc, cbMove) \
_PropMoveMemory(pvDst, pvSrc, cbMove) #endif
inline VOID _PropMoveMemory( #if DBGPROP
char *pszReason, VOID *pvSection, #endif
VOID *pvDst, VOID const *pvSrc, ULONG cbMove) { DebugTrace(0, Dbg, ( "%s: Moving Dst=%lx(%l" szX ") Src=%lx(%l" szX ") Size=%l" szX "\n", pszReason, pvDst, (BYTE *) pvDst - (BYTE *) pvSection, pvSrc, (BYTE *) pvSrc - (BYTE *) pvSection, cbMove)); RtlMoveMemory(pvDst, pvSrc, cbMove); }
inline BOOLEAN IsReadOnlyPropertySet(BYTE flags, BYTE state) { return( (flags & CREATEPROP_MODEMASK) == CREATEPROP_READ || (state & CPSS_USERDEFINEDDELETED) || (state & (CPSS_MULTIPLESECTIONS | CPSS_DOCUMENTSUMMARYINFO)) == CPSS_MULTIPLESECTIONS); }
inline BOOLEAN IsReadOnlyPropid(PROPID pid) { return( pid == PID_DICTIONARY || pid == PID_CODEPAGE || pid == PID_LOCALE || pid == PID_MODIFY_TIME || pid == PID_SECURITY); }
//+--------------------------------------------------------------------------
// Member: CStreamChunkList::CStreamChunkList
//
// Synopsis: constructor
//
// Arguments: [cChunks] -- count of chunks that will be needed
//
// Returns: None
//+--------------------------------------------------------------------------
CStreamChunkList::CStreamChunkList( ULONG cChunks, CStreamChunk *ascnk) : _cMaxChunks(cChunks), _cChunks(0), _ascnk(ascnk), _fDelete(FALSE) { }
//+--------------------------------------------------------------------------
// Member: CStreamChunkList::Delete
//
// Synopsis: destructor
//
// Arguments: None
//
// Returns: None
//+--------------------------------------------------------------------------
inline VOID CStreamChunkList::Delete(VOID) { if (_fDelete) { delete [] _ascnk; } #if DBGPROP
_cMaxChunks = _cChunks = 0; _ascnk = NULL; _fDelete = FALSE; #endif
}
//+--------------------------------------------------------------------------
// Member: CStreamChunkList::GetChunk
//
// Synopsis: retrieves a chunk given the index
//
// Arguments: [i] -- index of the chunk to retrieve
//
// Returns: specified chunk pointer
//+--------------------------------------------------------------------------
inline CStreamChunk const * CStreamChunkList::GetChunk(ULONG i) const { PROPASSERT(i < _cChunks); PROPASSERT(i < _cMaxChunks); PROPASSERT(_ascnk != NULL); return(&_ascnk[i]); }
//+--------------------------------------------------------------------------
// Member: CStreamChunkList::Count
//
// Synopsis: returns the count of chunks
//
// Arguments: None
//
// Returns: the number of chunks.
//+--------------------------------------------------------------------------
inline ULONG CStreamChunkList::Count(VOID) const { return(_cChunks); }
//+--------------------------------------------------------------------------
// Member: CStreamChunkList::GetFreeChunk
//
// Synopsis: gets a unused chunk descriptor
//
// Arguments: [pstatus] -- NTSTATUS code
//
// Returns: a ptr to a stream chunk descriptor.
// This will be NULL if there was an
// error.
//+--------------------------------------------------------------------------
CStreamChunk * CStreamChunkList::GetFreeChunk(OUT NTSTATUS *pstatus) { CStreamChunk *pscnk = NULL;
*pstatus = STATUS_SUCCESS;
PROPASSERT(_cChunks < _cMaxChunks); if (_ascnk == NULL) { PROPASSERT(_cChunks == 0); _ascnk = newk(mtPropSetStream, NULL) CStreamChunk[_cMaxChunks]; if (_ascnk == NULL) { StatusNoMemory(pstatus, "GetFreeChunk"); goto Exit; } _fDelete = TRUE; }
pscnk = &_ascnk[_cChunks++];
// ----
// Exit
// ----
Exit:
return( pscnk ); }
//+--------------------------------------------------------------------------
// Member: CStreamChunkList::AssertCbChangeTotal
//
// Synopsis: make sure the computed cbChangeTotal is correct for the chunk
//
// Arguments: None
//
// Returns: Nothing
//+--------------------------------------------------------------------------
#if DBGPROP
VOID CStreamChunkList::AssertCbChangeTotal( CStreamChunk const *pscnk, ULONG cbChangeTotal) const { ULONG cb = 0; ULONG i;
for (i = 0; i < Count(); i++) { CStreamChunk const *pscnkT = GetChunk(i);
cb += pscnkT->cbChange; if (pscnk == pscnkT) { PROPASSERT(cb == cbChangeTotal); return; } } PROPASSERT(i < Count()); } #endif
//+--------------------------------------------------------------------------
// Member: fnChunkCompare
//
// Synopsis: qsort helper to compare chunks in the chunk list.
//
// Arguments: [pscnk1] -- pointer to chunk1
// [pscnk2] -- pointer to chunk2
//
// Returns: difference
//+--------------------------------------------------------------------------
INT __cdecl fnChunkCompare(VOID const *pscnk1, VOID const *pscnk2) { return(((CStreamChunk const *) pscnk1)->oOld - ((CStreamChunk const *) pscnk2)->oOld); }
//+--------------------------------------------------------------------------
// Member: CStreamChunkList::SortByStartAddress
//
// Synopsis: sort all the chunks that are being modified in a stream in the
// ascending order.
//
// Arguments: None
//
// Returns: None
//+--------------------------------------------------------------------------
VOID CStreamChunkList::SortByStartAddress(VOID) { DebugTrace(0, Dbg, ("Sorting %l" szX " Chunks @%lx\n", _cChunks, _ascnk));
qsort(_ascnk, _cChunks, sizeof(_ascnk[0]), fnChunkCompare);
#if DBGPROP
LONG cbChangeTotal; ULONG i;
cbChangeTotal = 0; for (i = 0; i < _cChunks; i++) { cbChangeTotal += _ascnk[i].cbChange;
DebugTrace(0, Dbg, ( "Chunk[%l" szX "] oOld=%l" szX " cbChange=%s%l" szX " cbChangeTotal=%s%l" szX "\n", i, _ascnk[i].oOld, _ascnk[i].cbChange < 0? "-" : "", _ascnk[i].cbChange < 0? -_ascnk[i].cbChange : _ascnk[i].cbChange, cbChangeTotal < 0? "-" : "", cbChangeTotal < 0? -cbChangeTotal : cbChangeTotal)); } #endif
}
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_GetFormatidOffset
//
// Synopsis: Get a pointer to the (first) section header
//
// Arguments: None
//
// Returns: pointer to section header
//+--------------------------------------------------------------------------
inline FORMATIDOFFSET * CPropertySetStream::_GetFormatidOffset(ULONG iSection) const { return(&((FORMATIDOFFSET *) Add2Ptr(_pph, sizeof(*_pph)))[iSection]); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_GetSectionHeader
//
// Synopsis: Get a pointer to the (first) section header
//
// Arguments: None
//
// Returns: pointer to section header
//+--------------------------------------------------------------------------
inline PROPERTYSECTIONHEADER * CPropertySetStream::_GetSectionHeader(VOID) const { return((PROPERTYSECTIONHEADER *) Add2Ptr(_pph, _oSection)); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_GetSectionHeader
//
// Synopsis: Get a pointer to the specified section header
//
// Arguments: [iSection] -- section number
// [pstatus] -- Pointer to NTSTATUS code.
//
// Returns: pointer to specified section header
//+--------------------------------------------------------------------------
PROPERTYSECTIONHEADER * CPropertySetStream::_GetSectionHeader(ULONG iSection, OUT NTSTATUS *pstatus) { *pstatus = STATUS_SUCCESS; PROPERTYSECTIONHEADER *psh = NULL;
ULONG oSection = 0; // Assume no header
ULONG cbstm = _MSTM(GetSize)(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// Don't assume *any* class variables (except _pph) are loaded yet!
PROPASSERT(_HasPropHeader() || iSection == 0);
if (_HasPropHeader()) { PROPASSERT(iSection < _pph->reserved ); oSection = MAXULONG; if (cbstm >= CB_PROPERTYSETHEADER + (iSection + 1) * CB_FORMATIDOFFSET) { oSection = _GetFormatidOffset(iSection)->dwOffset; } } if (oSection != MAXULONG && cbstm >= oSection + CB_PROPERTYSECTIONHEADER) { psh = (PROPERTYSECTIONHEADER *) Add2Ptr(_pph, oSection);
if( cbstm >= oSection + psh->cbSection ) { goto Exit; } else { psh = NULL; } }
StatusCorruption(pstatus, "GetSectionHeader(i): stream size");
// ----
// Exit
// ----
Exit:
return(psh); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_SearchForCodePage, private
//
// Synopsis: Searches a section of a property set for the code page.
//
// This routine searches for the code page by iterating
// through the PID/Offset array in search of
// PID_CODEPAGE. The difference between calling
// this routine, and calling GetValue(PID_CODEPAGE),
// is that this routine does not assume that the
// property set is formatted correctly; it only assumes
// that the PID/Offset array is correct.
//
// Note that this routine is like a specialized _LoadProperty(),
// the important difference is that this routine must use
// unaligned pointers, since it cannot assume that the
// property set is aligned properly.
//
// Pre-Conditions:
// The PID/Offset array is correct.
// &&
// _oSection & _cSection are set correctly.
//
// Post-Conditions:
// If PID_CODEPAGE exists, it is put into _CodePage.
// If it doesn't exist, _CodePage is left unchanged.
//
// Arguments: [pstatus] -- Pointer to NTSTATUS code.
//
// Returns: None.
//+-------------------------------------------------------------------------
VOID CPropertySetStream::_SearchForCodePage( OUT NTSTATUS *pstatus ) {
PROPERTYSECTIONHEADER UNALIGNED *psh; PROPERTYIDOFFSET UNALIGNED *ppo; PROPERTYIDOFFSET UNALIGNED *ppoMax; #if i386 == 0
PROPERTYSECTIONHEADER shCopy; #endif
PROPERTYSECTIONHEADER *pshCopy; ULONG cbstm;
*pstatus = STATUS_SUCCESS;
// Verify the pre-conditions.
PROPASSERT( _oSection != 0 ); PROPASSERT( _cSection != 0 );
// It's invalid to call any function on a deleted
// DocSumInfo user-defined (section section) section.
if (_State & CPSS_USERDEFINEDDELETED) { StatusAccessDenied(pstatus, "GetValue: deleted"); goto Exit; }
// Get the section's header and first & last PID/Offset pointers.
// We can't use _LoadPropertyOffsetPointers, because it assumes
// alignment.
psh = _GetSectionHeader();
#if i386 == 0
memcpy(&shCopy, psh, sizeof(shCopy)); // note that the copy should not be used to access rgprop, which
// is a variable sized char arr.
pshCopy=&shCopy; #else
pshCopy = psh; // okay to access unaligned on i386
#endif
cbstm = _MSTM(GetSize)(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (cbstm < _oSection + CB_PROPERTYSECTIONHEADER || cbstm < _oSection + CB_PROPERTYSECTIONHEADER + pshCopy->cProperties * CB_PROPERTYIDOFFSET || cbstm < _oSection + pshCopy->cbSection) { StatusCorruption(pstatus, "_SearchForCodePage: stream size"); goto Exit; }
ppo = (PROPERTYIDOFFSET UNALIGNED *) Add2Ptr(psh, CB_PROPERTYSECTIONHEADER);
ppoMax = ppo + pshCopy->cProperties;
// Search the PID/Offset array for PID_CODEPAGE
for ( ; ppo < ppoMax; ppo++) { if (PIDOFFSET_GetPropid(ppo) == PID_CODEPAGE) { SERIALIZEDPROPERTYVALUE UNALIGNED *pprop; pprop = (SERIALIZEDPROPERTYVALUE *) _MapOffsetToAddress( PIDOFFSET_GetOffset(ppo) );
// Get the real address of serialized property.
// Check for corruption.
if ( ( ( PIDOFFSET_GetOffset(ppo) + CB_SERIALIZEDPROPERTYVALUE + sizeof(DWORD) ) > pshCopy->cbSection ) || PropByteSwap(SPV_GetType(pprop)) != VT_I2 ) { StatusCorruption(pstatus, "_SearchForCodePage"); goto Exit; }
// Set the member code page from the serialized property.
// (The codepage is an I2).
// we do memcpy to avoid alignment problems
memcpy(&_CodePage, SPV_GetRgb(pprop), sizeof(_CodePage)); _CodePage = PropByteSwap(_CodePage);
break;
} // if (PIDOFFSET_GetPropid(ppo) == PID_CODEPAGE)
} // for ( ; ppo < ppoMax; ppo++)
// ----
// Exit
// ----
Exit:
return;
} // CPropertySetStream::_SearchForCodePage()
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_MapAddressToOffset, private
//
// Synopsis: maps an address to an offset
//
// Arguments: [pvAddr] -- the address in the section
//
// Returns: section-relative offset for passed pointer
//+--------------------------------------------------------------------------
inline ULONG CPropertySetStream::_MapAddressToOffset(VOID const *pvAddr) const { PROPASSERT(_cSection != 0);
// Get a ptr to the section header.
VOID const *pvSectionHeader = _GetSectionHeader();
PROPASSERT((BYTE const *) pvAddr >= (BYTE const *) pvSectionHeader); return((BYTE const *) pvAddr - (BYTE const *) pvSectionHeader); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_MapAbsOffsetToAddress, private
//
// Synopsis: maps an address to an offset
//
// Arguments: [oAbsolute] -- the absolute offset
//
// Returns: a ptr to the offset mapped
//+--------------------------------------------------------------------------
inline VOID * CPropertySetStream::_MapAbsOffsetToAddress(ULONG oAbsolute) const { return(Add2Ptr(_pph, oAbsolute)); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_MapAddressToAbsOffset, private
//
// Synopsis: maps an address to an offset
//
// Arguments: [pvAddr] -- the address
//
// Returns: the absolute offset
//+--------------------------------------------------------------------------
inline ULONG CPropertySetStream::_MapAddressToAbsOffset(VOID const *pvAddr) const { PROPASSERT((BYTE const *) pvAddr >= (BYTE *) _pph); return((BYTE const *) pvAddr - (BYTE *) _pph); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::CPropertySetStream
//
// Synopsis: constructor for property set class
//
// Arguments:UK [Flags] -- NONSIMPLE|*1* of READ/WRITE/CREATE/CREATEIF/DELETE
// K [pscb] -- SCB for property stream
// K [pirpc] -- pointer to Irp Context
// K [State] -- CPSS_PROPHEADER
// U [pmstm] -- mapped stream implementation
// U [pma] -- caller's memory allocator
//
// Returns: None
//---------------------------------------------------------------------------
CPropertySetStream::CPropertySetStream( IN USHORT Flags, // NONSIMPLE|*1* of READ/WRITE/CREATE/CREATEIF/DELETE
IN CMappedStream *pmstm, // mapped stream impelementation
IN PMemoryAllocator *pma // caller's memory allocator
) : _Flags((BYTE) Flags), _State(0), _pmstm(pmstm), _pma(pma), _pph(NULL) { _CodePage = CP_CREATEDEFAULT(_State); // Default if not present
PROPASSERT(_Flags == Flags); // Should fit in a byte
_oSection = 0; _cSection = 0; _cbTail = 0; }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::Close
//
// Synopsis: shutdown property set prior to calling destructor
//
// Arguments: [pstatus] -- Pointer to NTSTATUS code.
//
// Returns: None
//---------------------------------------------------------------------------
VOID CPropertySetStream::Close(OUT NTSTATUS *pstatus) { *pstatus = STATUS_SUCCESS;
// Validate the byte-order (_pph could be NULL in certain
// close scenarios, e.g. an RtlCreatePropertySet fails).
PROPASSERT(NULL == _pph || PROPSET_BYTEORDER == _pph->wByteOrder); PROPASSERT( (_Flags & CREATEPROP_MODEMASK) != CREATEPROP_READ || !IsModified());
_MSTM(Unmap)(IsModified(), (VOID **) &_pph);
_MSTM(Close)(pstatus); // if( !NT_SUCCESS(*pstatus) ) goto Exit;
//Exit:
return; }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::Open
//
// Synopsis: Open property set image
//
// Arguments: None
//
// Returns: None
//---------------------------------------------------------------------------
VOID CPropertySetStream::Open( IN GUID const *pfmtid, // property set fmtid
OPTIONAL IN GUID const *pclsid, // CLASSID of propset code (create only)
IN ULONG LocaleId, // Locale Id (create only)
OPTIONAL OUT ULONG *pOSVersion, // OS Version from header
IN USHORT CodePage, // CodePage of property set (create only)
OUT NTSTATUS *pstatus ) { *pstatus = STATUS_SUCCESS; LOADSTATE LoadState; PROPASSERT(!_IsMapped());
if( pOSVersion != NULL ) *pOSVersion = PROPSETHDR_OSVERSION_UNKNOWN;
// Open the underlying stream which holds the property set.
// We give it a callback pointer so that it can call
// RtlOnMappedStreamEvent.
_MSTM(Open)(this, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// Load the header, including fixing the in-memory image of
// poorly-formatted property sets.
LoadState = _LoadHeader(pfmtid, _Flags & CREATEPROP_MODEMASK, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (LoadState != LOADSTATE_DONE) { switch (_Flags & CREATEPROP_MODEMASK) { case CREATEPROP_READ: case CREATEPROP_WRITE: if (LoadState == LOADSTATE_FAIL) { StatusCorruption(pstatus, "Open: _LoadHeader"); goto Exit; } PROPASSERT( LoadState == LOADSTATE_BADFMTID || LoadState == LOADSTATE_USERDEFINEDNOTFOUND); DebugTrace(0, DEBTRACE_ERROR, ( "_LoadHeader: LoadState=%x\n", LoadState));
*pstatus = STATUS_PROPSET_NOT_FOUND; goto Exit; }
_Create( pfmtid, pclsid, LocaleId, CodePage, LoadState, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit;
} // if (LoadState != LOADSTATE_DONE)
PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder);
if (_HasPropHeader() && (_pph->dwOSVer == PROPSETVER_WIN310 || _pph->dwOSVer == PROPSETVER_WIN333)) { DebugTrace(0, DEBTRACE_PROPPATCH, ( "Open(%s) downlevel: %x\n", (_Flags & CREATEPROP_MODEMASK) == CREATEPROP_READ? "Read" : "Write", _Flags)); _State |= CPSS_DOWNLEVEL; }
if ((_Flags & CREATEPROP_MODEMASK) != CREATEPROP_READ) { if (_State & CPSS_PACKEDPROPERTIES) { StatusAccessDenied(pstatus, "Open: writing Unaligned propset"); goto Exit; } if ((_State & (CPSS_MULTIPLESECTIONS | CPSS_DOCUMENTSUMMARYINFO)) == CPSS_MULTIPLESECTIONS) { StatusAccessDenied(pstatus, "Open: writing unknown multiple section propset"); goto Exit; } }
// Return the OS Version to the caller.
if( pOSVersion != NULL ) *pOSVersion = _pph->dwOSVer;
// ----
// Exit
// ----
Exit:
return; }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::ReOpen
//
// Synopsis: ReOpen property set image
//
// Arguments: [pstatus] -- Pointer to NSTATUS code.
//
// Returns: Number of properties.
//---------------------------------------------------------------------------
ULONG CPropertySetStream::ReOpen(OUT NTSTATUS *pstatus) { LOADSTATE LoadState; PROPERTYSECTIONHEADER const *psh; ULONG cProperties = 0;
*pstatus = STATUS_SUCCESS;
PROPASSERT(_IsMapped());
_MSTM(ReOpen)((VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (_State & CPSS_USERDEFINEDDELETED) { goto Exit; }
LoadState = _LoadHeader(NULL, CREATEPROP_READ, // all we need is !create
pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (LoadState != LOADSTATE_DONE) { DebugTrace(0, DEBTRACE_ERROR, ( "ReOpen: LoadState=%lx\n", LoadState)); StatusCorruption(pstatus, "ReOpen: _LoadHeader"); goto Exit; }
PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder);
psh = _GetSectionHeader(); PROPASSERT(psh != NULL);
cProperties = psh->cProperties;
// ----
// Exit
// ----
Exit:
return( cProperties ); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_InitSection
//
// Synopsis: Initialize a section header and the default properties.
//
// Arguments: [pfo] -- pointer to section info
// [LocaleId] -- Locale Id
//
// Returns: None
//---------------------------------------------------------------------------
// Serialized Code-Page size
#define CB_CODEPAGE (sizeof(ULONG) + DwordAlign(sizeof(USHORT)))
// Serialized Locale ID (LCID) size.
#define CB_LOCALE (sizeof(ULONG) + sizeof(ULONG))
// Minimum section size (minimum has Code Page & LCID)
#define CB_MINSECTIONSIZE (CB_PROPERTYSECTIONHEADER \
+ 2 * CB_PROPERTYIDOFFSET \ + CB_CODEPAGE \ + CB_LOCALE)
// Minimum serialized dictionary size (a dict with no entries).
#define CB_EMPTYDICTSIZE (sizeof(DWORD)) // Entry count
// Minimum User-Defined section size (in DocumentSummaryInformation propset).
// (Must include an empty dictionary & a PID/Offset for it.)
#define CB_MINUSERDEFSECTIONSIZE \
(CB_MINSECTIONSIZE \ + \ CB_PROPERTYIDOFFSET \ + \ CB_EMPTYDICTSIZE)
VOID CPropertySetStream::_InitSection( IN FORMATIDOFFSET *pfo, IN ULONG LocaleId, IN BOOL fCreateDictionary // Create an empty dictionary?
) { PROPERTYSECTIONHEADER *psh;
ULONG ulPropIndex; // Index into the PID/Offset array.
DWORD dwPropValOffset; // The offset to where the next prop val will be written.
// Pointer to a serialized property value.
SERIALIZEDPROPERTYVALUE *pprop;
psh = (PROPERTYSECTIONHEADER *) _MapAbsOffsetToAddress(pfo->dwOffset);
// Set the property count and section size in the section header.
// This must account for the Code Page and Locale ID properties, and
// might need to account for an empty dictionary property.
// dwPropValOffset identifies the location of the next property value
// to be written.
if( fCreateDictionary ) { // Three properties: Code Page, LCID, and Dictionary.
psh->cProperties = 3; dwPropValOffset = CB_PROPERTYSECTIONHEADER + 3 * CB_PROPERTYIDOFFSET; psh->cbSection = CB_MINUSERDEFSECTIONSIZE; } else { // Two properties: Code Page and LCID (no dictionary).
psh->cProperties = 2; dwPropValOffset = CB_PROPERTYSECTIONHEADER + 2 * CB_PROPERTYIDOFFSET; psh->cbSection = CB_MINSECTIONSIZE; }
ulPropIndex = 0;
// If requested by the caller, create a dictionary property, but
// leave the dictionary empty. We always create this first. It shouldn't
// matter where it's located, but Office95 requires it
// and it doesn't do any harm to put it there.
if( fCreateDictionary ) { // Fill in the PID/Offset table.
psh->rgprop[ ulPropIndex ].propid = PID_DICTIONARY; psh->rgprop[ ulPropIndex ].dwOffset = dwPropValOffset;
// Fill in the property value.
pprop = (SERIALIZEDPROPERTYVALUE *) Add2Ptr( psh, dwPropValOffset ); pprop->dwType = 0L; // For the dictonary, this is actually the entry count.
// Advance the table & value indices.
ulPropIndex++; dwPropValOffset += CB_EMPTYDICTSIZE;
} // if( fCreateDictionary )
// Write the code page. We write a zero first to initialize
// the padding bytes.
psh->rgprop[ ulPropIndex ].propid = PID_CODEPAGE; psh->rgprop[ ulPropIndex ].dwOffset = dwPropValOffset;
pprop = (SERIALIZEDPROPERTYVALUE *) Add2Ptr( psh, dwPropValOffset ); pprop->dwType = PropByteSwap((DWORD) VT_I2); *(DWORD *) pprop->rgb = 0; // Zero out extra two bytes.
*(WORD *) pprop->rgb = PropByteSwap( _CodePage ); ulPropIndex++; dwPropValOffset += CB_CODEPAGE;
// Write the Locale ID.
psh->rgprop[ ulPropIndex ].propid = PID_LOCALE; psh->rgprop[ ulPropIndex ].dwOffset = dwPropValOffset;
pprop = (SERIALIZEDPROPERTYVALUE *) Add2Ptr(psh, dwPropValOffset ); pprop->dwType = PropByteSwap( (DWORD) VT_UI4 ); *(DWORD *) pprop->rgb = PropByteSwap( (DWORD) LocaleId );
}
//+---------------------------------------------------------------------------
// Member: CPropertySetStream:: _MultiByteToWideChar, private
//
// Synopsis: Convert a MultiByte string to a Unicode string,
// using the _pma memory allocator if necessary.
//
// Arguments: [pch] -- pointer to MultiByte string
// [cb] -- byte length of MultiByte string
// (-1 if null terminated)
// [CodePage] -- Codepage of input string.
// [ppwc] -- pointer to pointer to converted string
// (if *ppwc is NULL, it will be alloced,
// if non-NULL, *ppwc must be *pcb bytes long).
// [pcb] -- IN: byte length of *ppwc
// OUT: byte length of Unicode string.
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: Nothing
//---------------------------------------------------------------------------
VOID CPropertySetStream::_MultiByteToWideChar( IN CHAR const *pch, IN ULONG cb, IN USHORT CodePage, OUT WCHAR **ppwc, OUT ULONG *pcb, OUT NTSTATUS *pstatus) { // ------
// Locals
// ------
// Did we allocate *ppwc?
BOOL fAlloc = FALSE;
// --------------
// Initialization
// --------------
*pstatus = STATUS_SUCCESS;
PROPASSERT(pch != NULL); PROPASSERT(ppwc != NULL); PROPASSERT(pcb != NULL);
PROPASSERT(IsAnsiString(pch, ((ULONG)-1 == cb ) ? MAXULONG : cb));
PROPASSERT(NULL != *ppwc || 0 == *pcb); PROPASSERT(UnicodeCallouts.pfnMultiByteToWideChar != NULL);
// ------------------
// Convert the String
// ------------------
// We will pass through this loop once (if the caller provided a buffer
// or twice (otherwise).
while (TRUE) { // Attempt to convert the string.
*pcb = (*UnicodeCallouts.pfnMultiByteToWideChar)( CodePage, // Source codepage
0, // Flags
pch, // Source string
cb, // Source string length
*ppwc, // Target string
*pcb); // Size of target string buffer
// The converted length should never be zero.
if (0 == *pcb) { // If we alloced a buffer, free it now.
if( fAlloc ) { _pma->Free( *ppwc ); *ppwc = NULL; }
// If there was an error, assume that it was a code-page
// incompatibility problem.
StatusError(pstatus, "_MultiByteToWideChar error", STATUS_UNMAPPABLE_CHARACTER); goto Exit; }
// There was no error. If we provided a non-NULL buffer,
// then the conversion was performed and we're done.
*pcb *= sizeof(WCHAR); // cch => cb
if (*ppwc != NULL) { DebugTrace(0, DEBTRACE_PROPERTY, ( "_MultiByteToWideChar: pch='%s'[%x] pwc='%ws'[%x->%x]\n", pch, cb, *ppwc, *pcb, *pcb * sizeof(WCHAR))); break; }
// We haven't actually the string yet. Now that
// we know the length, we can allocate a buffer and try the
// conversion for real.
*ppwc = (WCHAR *) _pma->Allocate( *pcb ); if (NULL == *ppwc) { StatusNoMemory(pstatus, "_MultiByteToWideChar: no memory"); goto Exit; } fAlloc = TRUE;
} // while(TRUE)
// ----
// Exit
// ----
Exit:
return;
} // CPropertySetStream::_MultiByteToWideChar
//+---------------------------------------------------------------------------
// Member: CPropertySetStream::_WideCharToMultiByte, private
//
// Synopsis: Convert a Unicode string to a MultiByte string,
// using the _pma memory allocator if necessary.
//
// Arguments: [pwc] -- pointer to Unicode string
// [cch] -- character length of Unicode string
// (-1 if null terminated)
// [CodePage] -- codepage of target string
// [ppch] -- pointer to pointer to converted string
// (if *ppch is NULL, it will be alloced,
// if non-NULL, *ppch must be *pcb bytes long).
// [pcb] -- IN: byte length of *ppch
// OUT: byte length of MultiByte string
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: Nothing
//---------------------------------------------------------------------------
VOID CPropertySetStream::_WideCharToMultiByte( IN WCHAR const *pwc, IN ULONG cch, IN USHORT CodePage, OUT CHAR **ppch, OUT ULONG *pcb, OUT NTSTATUS *pstatus) { // ------
// Locals
// ------
// Did we allocate *ppch?
BOOL fAlloc = FALSE;
// --------------
// Initialization
// --------------
*pstatus = STATUS_SUCCESS;
PROPASSERT(pwc != NULL); PROPASSERT(ppch != NULL); PROPASSERT(pcb != NULL);
PROPASSERT(IsUnicodeString(pwc, ((ULONG)-1 == cch ) ? MAXULONG : cch*sizeof(WCHAR)));
PROPASSERT(NULL != *ppch || 0 == *pcb); PROPASSERT(UnicodeCallouts.pfnWideCharToMultiByte != NULL);
// ------------------
// Convert the String
// ------------------
// We will pass through this loop once (if the caller provided a buffer
// or twice (otherwise).
while (TRUE) { // Attempt the conversion.
*pcb = (*UnicodeCallouts.pfnWideCharToMultiByte)( CodePage, // Codepage to convert to
0, // Flags
pwc, // Source string
cch, // Size of source string
*ppch, // Target string
*pcb, // Size of target string buffer
NULL, // lpDefaultChar
NULL); // lpUsedDefaultChar
// A converted length of zero indicates an error.
if (0 == *pcb) { // If we allocated a buffer in this routine, free it.
if( fAlloc ) { _pma->Free( *ppch ); *ppch = NULL; }
// If there was an error, assume that it was a code-page
// incompatibility problem.
StatusError(pstatus, "_WideCharToMultiByte: WideCharToMultiByte error", STATUS_UNMAPPABLE_CHARACTER); goto Exit; }
// If we have a non-zero length, and we provided a buffer,
// then we're done (successfully).
if (*ppch != NULL) { DebugTrace(0, DEBTRACE_PROPERTY, ( "_WideCharToMultiByte: pwc='%ws'[%x] pch='%s'[%x->%x]\n", pwc, cch, *ppch, *pcb, *pcb)); break; }
// There were no errors, but we need to allocate a buffer
// to do the actual conversion.
*ppch = (CHAR*) _pma->Allocate( *pcb ); if (*ppch == NULL) { StatusNoMemory(pstatus, "_WideCharToMultiByte: no memory"); goto Exit; } fAlloc = TRUE;
} // while (TRUE)
// ----
// Exit
// ----
Exit:
return;
} // CPropertySetStream::_WideCharToMultiByte
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::ByteSwapHeaders
//
// Synopsis: Byte-swap the headers of a property set header
// (both the propset header and any section headers).
//
// Arguments: [PROPERTYSETHEADER*] pph
// Pointer to the beginning of the property set.
// [ULONG] cbstm
// Total size of the property stream.
// [NTSTATUS*] pstatus
// Pointer to NTSTATUS code.
//
// Pre-Conditions:
// There are no more than two sections.
//
// Note that this routine does not assume anything
// about the current state of the CPropertySetStream
// (it accesses no member variables).
//
// Post-Conditions:
// If the byte-order indicator is valid, the
// propset and section headers are byte-swapped
//
// Returns: None. *pstatus will only be non-successful
// if the Stream was too small for the property set
// (i.e, the property set is corrupt). If the caller
// knows this not to be the case, then it can assume
// that this routine will return STATUS_SUCCESS.
//
//---------------------------------------------------------------------------
VOID CPropertySetStream::ByteSwapHeaders( IN PROPERTYSETHEADER *pph, IN DWORD cbstm, OUT NTSTATUS *pstatus ) { #if LITTLEENDIAN
*pstatus = STATUS_SUCCESS; return;
#else
// ------
// Locals
// ------
ULONG cSections; ULONG ulIndex, ulSectionIndex;
// pfoPropSet points into pph, pfoReal is a local copy
// in the system's endian-ness.
FORMATIDOFFSET *pfoPropSet, pfoReal[2];
// Pointers into pph.
PROPERTYSECTIONHEADER *psh = NULL; PROPERTYIDOFFSET *po = NULL;
// Are we converting *to* the system's endian-ness?
BOOL fToSystemEndian;
// ----------
// Initialize
// ----------
*pstatus = STATUS_SUCCESS; PROPASSERT( NULL != pph ); PROPASSERT(PROPSET_BYTEORDER == pph->wByteOrder || PROPSET_BYTEORDER == ByteSwap( pph->wByteOrder ) );
// ----------------------------
// Swap the Property Set header
// ----------------------------
// Validate the stream length.
if( sizeof(*pph) > cbstm ) { StatusCorruption(pstatus, "CPropertySetStream::ByteSwapHeaders: PropertySet header size"); goto Exit; }
// Swap the fields in place.
PropByteSwap( &pph->wByteOrder ); PropByteSwap( &pph->wFormat ); PropByteSwap( &pph->dwOSVer ); PropByteSwap( &pph->clsid ); PropByteSwap( &pph->reserved );
// Are we converting to little-endian?
if( PROPSET_BYTEORDER == pph->wByteOrder) fToSystemEndian = TRUE; else { fToSystemEndian = FALSE; PROPASSERT( PROPSET_BYTEORDER == PropByteSwap(pph->wByteOrder) ); }
// Get the correctly-endianed section count and validate.
cSections = fToSystemEndian ? pph->reserved : PropByteSwap( pph->reserved );
if( cSections > 2 ) { StatusCorruption(pstatus, "CPropertySetStream::ByteSwapHeaders: PropertySet header size"); goto Exit; }
// -------------------------
// Swap the per-section data
// -------------------------
pfoPropSet = (FORMATIDOFFSET*) ((BYTE*) pph + sizeof(*pph)); for( ulSectionIndex = 0; ulSectionIndex < cSections; ulSectionIndex++ ) { ULONG cbSection, cProperties;
// ------------------------------
// Swap the FormatID/Offset entry
// ------------------------------
// Is the Stream long enough for the array?
if( cbstm < (ULONG) &pfoPropSet[ulSectionIndex] + sizeof(*pfoPropSet) - (ULONG) pph ) { StatusCorruption(pstatus, "CPropertySetStream::_ByteSwapHeaders: FormatID/Offset size"); goto Exit; }
// Swap this FMTID/Offset entry in place.
PropByteSwap( &pfoPropSet[ulSectionIndex].fmtid ); PropByteSwap( &pfoPropSet[ulSectionIndex].dwOffset );
// Get a local copy of this array entry.
// Since we just swapped these values, we may need to swap the
// local copies back in order to make them usable.
pfoReal[ ulSectionIndex ].fmtid = pfoPropSet[ulSectionIndex].fmtid; pfoReal[ ulSectionIndex ].dwOffset = pfoPropSet[ulSectionIndex].dwOffset;
if( !fToSystemEndian ) { PropByteSwap( &pfoReal[ulSectionIndex].fmtid ); PropByteSwap( &pfoReal[ulSectionIndex].dwOffset ); }
// -----------------------
// Swap the section header
// -----------------------
// Locate the section header and the first entry in the
// PID/Offset table.
psh = (PROPERTYSECTIONHEADER*) ( (BYTE*) pph + pfoReal[ ulSectionIndex ].dwOffset ); po = (PROPERTYIDOFFSET*) ( (BYTE*) psh + sizeof(psh->cbSection) + sizeof(psh->cProperties) );
// Validate that we can see up to the PID/Offset table.
if( cbstm < (ULONG) ((BYTE*) po - (BYTE*) pph) ) { StatusCorruption(pstatus, "CPropertySetStream::ByteSwapHeaders: Section header size"); goto Exit; }
// Swap the two fields at the top of the section header,
// and make local copies. Again, we may need to swap the
// local copies to make them usable.
cbSection = psh->cbSection = PropByteSwap(psh->cbSection); cProperties = psh->cProperties = PropByteSwap( psh->cProperties );
if( !fToSystemEndian) { PropByteSwap( &cbSection ); PropByteSwap( &cProperties ); }
// -------------------------
// Swap the PID/Offset table
// -------------------------
// Validate that we can see the whole table.
if( cbstm < (BYTE*) po - (BYTE*) pph + cProperties * sizeof(*po) ) { StatusCorruption(pstatus, "CPropertySetStream::ByteSwapHeaders: Section header size"); goto Exit; }
// Swap each of the array entries.
for( ulIndex = 0; ulIndex < cProperties; ulIndex++ ) { PropByteSwap( &po[ulIndex].propid ); PropByteSwap( &po[ulIndex].dwOffset ); }
} // for( ulSectionIndex = 0; ulSectionIndex < cSections, ulIndex++ )
// ----
// Exit
// ----
Exit:
return;
#endif // #if LITTLEENDIAN ... #else
} // CPropertySetStream::ByteSwapHeaders
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_CreateUserDefinedSection
//
// Synopsis: Create second property section
//
// Arguments: [LoadState] -- _LoadHeader returned state
// [LocaleId] -- Locale Id
// [pstatus] -- Pointer to NTSTATUS code.
//
// Returns: TRUE if LoadState handled successfully. If TRUE,
// *pstatus will be STATUS_SUCCESS.
//---------------------------------------------------------------------------
BOOLEAN CPropertySetStream::_CreateUserDefinedSection( IN LOADSTATE LoadState, IN ULONG LocaleId, OUT NTSTATUS *pstatus) { BOOL fSuccess = FALSE; FORMATIDOFFSET *pfo; ULONG cbstmNew; PROPERTYSECTIONHEADER *psh;
*pstatus = STATUS_SUCCESS;
PROPASSERT(_State & CPSS_USERDEFINEDPROPERTIES); switch (_Flags & CREATEPROP_MODEMASK) { case CREATEPROP_CREATEIF: case CREATEPROP_CREATE: if (LoadState == LOADSTATE_USERDEFINEDNOTFOUND) { ULONG cbmove;
PROPASSERT(_cSection == 1); pfo = _GetFormatidOffset(0); PROPASSERT(pfo->fmtid == guidDocumentSummary); PROPASSERT(IsDwordAligned(pfo->dwOffset));
// Get a pointer to the first section header, using the
// FmtID/Offset array.
psh = (PROPERTYSECTIONHEADER *) _MapAbsOffsetToAddress(pfo->dwOffset);
// Determine if we need to move the first section back in order
// to make room for this new entry in the FmtID/Offset array.
cbmove = 0; if (pfo->dwOffset < CB_PROPERTYSETHEADER + 2 * CB_FORMATIDOFFSET) { cbmove = CB_PROPERTYSETHEADER + 2*CB_FORMATIDOFFSET - pfo->dwOffset; }
// How big should the Stream be?
cbstmNew = pfo->dwOffset // The offset of the first section
+ cbmove // Room for new FormatID/Offset array entry
+ // Size of first section
DwordAlign(psh->cbSection) + // Size of User-Defined section.
CB_MINUSERDEFSECTIONSIZE;
// Set the stream size.
_MSTM(SetSize)(cbstmNew, TRUE, (VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// reload all pointers into mapped image:
pfo = _GetFormatidOffset(0); psh = (PROPERTYSECTIONHEADER *) _MapAbsOffsetToAddress(pfo->dwOffset);
if (cbmove != 0) { // Move section back to make room for new FORMATIDOFFSET entry
PropMoveMemory( "_AddSection", psh, Add2Ptr(psh, cbmove), psh, psh->cbSection);
pfo->dwOffset += cbmove; PROPASSERT(IsDwordAligned(pfo->dwOffset)); }
psh->cbSection = DwordAlign(psh->cbSection);
PROPASSERT(_oSection == 0); PROPASSERT(_cSection == 1); PROPASSERT(_pph->reserved == 1);
_cSection++; _pph->reserved++;
_oSection = pfo->dwOffset + psh->cbSection; pfo = _GetFormatidOffset(1); pfo->fmtid = guidDocumentSummarySection2; pfo->dwOffset = _oSection; _InitSection(pfo, LocaleId, TRUE ); // Create an empty dictionary.
fSuccess = TRUE; } break;
case CREATEPROP_DELETE: PROPASSERT( LoadState == LOADSTATE_USERDEFINEDDELETE || LoadState == LOADSTATE_USERDEFINEDNOTFOUND); if (LoadState == LOADSTATE_USERDEFINEDDELETE) { PROPASSERT(_cSection == 2); PROPASSERT(_pph->reserved == 2); pfo = _GetFormatidOffset(1); RtlZeroMemory(pfo, sizeof(*pfo));
_cSection--; _pph->reserved--; pfo = _GetFormatidOffset(0); PROPASSERT(pfo->fmtid == guidDocumentSummary); PROPASSERT(IsDwordAligned(pfo->dwOffset)); psh = (PROPERTYSECTIONHEADER *) _MapAbsOffsetToAddress(pfo->dwOffset); psh->cbSection = DwordAlign(psh->cbSection); cbstmNew = pfo->dwOffset + psh->cbSection;
_MSTM(SetSize)(cbstmNew, TRUE, (VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; } _State |= CPSS_USERDEFINEDDELETED;
fSuccess = TRUE; break;
default: PROPASSERT(!"_Flags: bad open mode"); }
// ----
// Exit
// ----
Exit:
return( fSuccess ); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_Create
//
// Synopsis: Create property set image
//
// Arguments: [pfmtid] -- format id
// [pclsid] -- class id
// [LocaleId] -- Locale Id
// [CodePage] -- CodePage
// [LoadState] -- _LoadHeader returned state
//
// Returns: None
//---------------------------------------------------------------------------
VOID CPropertySetStream::_Create( IN GUID const *pfmtid, OPTIONAL IN GUID const *pclsid, IN ULONG LocaleId, // Locale Id (create only)
IN USHORT CodePage, IN LOADSTATE LoadState, OUT NTSTATUS *pstatus ) { ULONG cb; FORMATIDOFFSET *pfo;
*pstatus = STATUS_SUCCESS;
_SetModified();
// Set the size of the stream to correspond to the header for the
// property set as well as the section.
_CodePage = CodePage; ULONG cSectionT = 1;
// Are we creating the UserDefined property set
// (the second section of the DocumentSummaryInformation
// property set)?
if (_State & CPSS_USERDEFINEDPROPERTIES) { // Create the UD propset, and set the cSection.
// If this routine returns TRUE, it means that
// the first section already existed, and we're done.
// Otherwise, we must continue and create the first section.
if (_CreateUserDefinedSection(LoadState, LocaleId, pstatus)) { // If we get here, we know that *pstatus is Success.
if (pclsid != NULL) { _pph->clsid = *pclsid; } goto Exit; } if( !NT_SUCCESS(*pstatus) ) goto Exit;
cSectionT = 2; }
// Calculate the exact size of the Stream (we know exactly
// what it will be because we only initialize the set(s) with
// fixed size data).
PROPASSERT( 1 <= cSectionT && cSectionT <= 2 ); cb = CB_PROPERTYSETHEADER // The size of the propset header.
+ // The size of the FmtID/Offset array
cSectionT * CB_FORMATIDOFFSET + CB_MINSECTIONSIZE // The size of the first section
+ // Maybe the size of the User-Defined section
( cSectionT <= 1 ? 0 : CB_MINUSERDEFSECTIONSIZE );
DebugTrace(0, Dbg, ("SetSize(%x) init\n", cb));
// Set the size of the stream
_MSTM(SetSize)(cb, TRUE, (VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// And get a mapping of the Stream.
_MSTM(Map)(TRUE, (VOID **) &_pph); RtlZeroMemory(_pph, cb); // Zeros classid, fmtid(s), etc
// Initialize the header
_pph->wByteOrder = 0xfffe; //_pph->wFormat = 0; // RtlZeroMemory does this
PROPASSERT(_pph->wFormat == 0); _pph->dwOSVer = PROPSETVER_CURRENT; if (pclsid != NULL) { _pph->clsid = *pclsid; } _pph->reserved = cSectionT;
// Initialize the format id offset for the section(s).
pfo = _GetFormatidOffset(0); pfo->dwOffset = CB_PROPERTYSETHEADER + cSectionT * CB_FORMATIDOFFSET;
// Are we creating the second section of the DocSumInfo property set?
if (cSectionT == 2) { // We need to initialize any empty first section.
pfo->fmtid = guidDocumentSummary;
_InitSection(pfo, LocaleId, FALSE); // Don't create an empty dictionary.
// Advance the FmtID/Offset table pointer to the second entry,
// and set it's offset to just beyond the first section.
pfo = _GetFormatidOffset(1); pfo->dwOffset = CB_PROPERTYSETHEADER + cSectionT * CB_FORMATIDOFFSET + CB_MINSECTIONSIZE; }
// Initialize the requested property set.
PROPASSERT(pfmtid != NULL); pfo->fmtid = *pfmtid; _InitSection(pfo, LocaleId, // TRUE => Create an empty dictionary
pfo->fmtid == guidDocumentSummarySection2 );
_cSection = cSectionT; _oSection = pfo->dwOffset;
// ----
// Exit
// ----
Exit:
return;
} // CPropertySetStream::_Create
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_LoadHeader
//
// Synopsis: verify header of a property set and read the code page
//
// Arguments: [pfmtid] -- format id
// [Mode] -- open mode
// [pstatus] -- Pointer to NTSTATUS code.
//
// Returns: LOADSTATE
//---------------------------------------------------------------------------
LOADSTATE CPropertySetStream::_LoadHeader( OPTIONAL IN GUID const *pfmtid, IN BYTE Mode, OUT NTSTATUS *pstatus) { LOADSTATE loadstate = LOADSTATE_FAIL; ULONG cbstm, cbMin; PROPERTYSECTIONHEADER *psh; FORMATIDOFFSET const *pfo; BOOLEAN fSummaryInformation = FALSE; #if DBGPROP
BOOLEAN fFirst = _pph == NULL; #endif
*pstatus = STATUS_SUCCESS;
PROPASSERT((_State & CPSS_USERDEFINEDDELETED) == 0);
// If this is one of the DocSumInfo property sets,
// we need to set some _State bits. If this is an
// Open, rather than a Create, pfmtid may be NULL.
// In that case, we'll set these bits after the open
// (since we can then get the fmtid from the header).
if( pfmtid != NULL && *pfmtid == guidDocumentSummary ) { _State |= CPSS_DOCUMENTSUMMARYINFO; }
if (pfmtid != NULL && *pfmtid == guidDocumentSummarySection2) { _State |= CPSS_USERDEFINEDPROPERTIES; } else { // If this isn't the UD property set, the Mode
// better not be "Delete" (all other property sets
// are deleted simply be deleting the underlying
// stream).
if (Mode == CREATEPROP_DELETE) { DebugTrace(0, Dbg, ("_LoadHeader: CREATEPROP_DELETE\n")); StatusInvalidParameter(pstatus, "_LoadHeader: CREATEPROP_DELETE"); goto Exit; } if (Mode == CREATEPROP_CREATE) { goto Exit; // We're going to overwrite it anyway
} }
// Get the size of the underlying stream.
cbstm = _MSTM(GetSize)(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// Map the serialized property set to a pointer.
_MSTM(Map)(FALSE, (VOID **) &_pph);
cbMin = _ComputeMinimumSize(cbstm, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// The following assert should technically ASSERT equality. However,
// to avoid unmapping and closing sections for every property operation,
// we allow shrinks to fail when other instances of the same property
// set are active. So we on occasion will legitimately see streams larger
// than necessary. The wasted space will be cleaned up when the property
// set is next modified.
//PROPASSERT(cbMin == cbstm);
PROPASSERT(cbMin <= cbstm); DebugTrace(0, KERNELSELECT(Dbg, Dbg | DEBTRACE_CREATESTREAM), ( "ComputeMinimumSize: cbMin=%l" szX " cbstm=%l" szX " cbUnused=%l" szX "\n", cbMin, cbstm, cbstm - cbMin));
_oSection = 0; _cSection = 1; _cbTail = 0;
if (_HasPropHeader()) { // The first expression must be TRUE before we can dereference _pph
// for the second expression.
if (cbstm < CB_PROPERTYSETHEADER + CB_FORMATIDOFFSET || cbstm < CB_PROPERTYSETHEADER + _pph->reserved * CB_FORMATIDOFFSET || _pph->wByteOrder != 0xfffe || _pph->wFormat != 0 || _pph->reserved < 1) { _cSection = 0; // Mark property set invalid
DebugTrace(0, cbstm != 0? DEBTRACE_ERROR : Dbg, ( "_LoadHeader: %s (ver=%lx)\n", cbstm == 0? "Empty Stream" : cbstm < CB_PROPERTYSETHEADER + CB_FORMATIDOFFSET? "Stream too small for header" : _pph->wByteOrder != 0xfffe? "Bad wByteOrder field" : _pph->wFormat != 0? "Bad wFormat field" : _pph->reserved < 1? "Bad reserved field" : "Bad dwOSVer field", _pph != NULL? _pph->dwOSVer : 0)); goto Exit; }
// Now that we've loaded the property set, check again
// to see if this is a SumInfo or DocSumInfo set.
pfo = _GetFormatidOffset(0); if (pfo->fmtid == guidDocumentSummary) { _State |= CPSS_DOCUMENTSUMMARYINFO; } else if (pfo->fmtid == guidSummary) { fSummaryInformation = TRUE; }
// If what we're after is the property set in the
// second section, verify that it's there.
if (_State & CPSS_USERDEFINEDPROPERTIES) { // Ensure that this is the second section of
// the DocSumInfo property set; that's the only
// two-section property set we support.
if ((_State & CPSS_DOCUMENTSUMMARYINFO) == 0) { DebugTrace(0, DEBTRACE_ERROR, ("Not DocumentSummaryInfo 1st FMTID\n")); goto Exit; }
// Verify that this proeprty set has two sections, and that
// the second section is the UD propset.
if (_pph->reserved < 2 || (pfo = _GetFormatidOffset(1))->fmtid != guidDocumentSummarySection2) { DebugTrace( 0, _pph->reserved < 2? Dbg : DEBTRACE_ERROR, ("Bad/missing 2nd section FMTID\n")); loadstate = LOADSTATE_USERDEFINEDNOTFOUND; goto Exit; } } else if (pfmtid != NULL) { // This isn't the UserDefined property set, so it
// should be the first section, so it should match
// the caller-requested format ID.
if (*pfmtid != pfo->fmtid) { // The propset's FmtID doesn't match, but maybe that's
// because it's a MacWord6 SumInfo property set, in which
// the FmtID isn't byte-swapped. Otherwise, it's a problem.
if( OSKIND_MACINTOSH == PROPSETHDR_OSVER_KIND(_pph->dwOSVer) && guidSummary == *pfmtid && IsEqualFMTIDByteSwap( *pfmtid, pfo->fmtid ) ) { fSummaryInformation = TRUE; } else { _cSection = 0; DebugTrace(0, DEBTRACE_ERROR, ("Bad FMTID\n")); loadstate = LOADSTATE_BADFMTID; goto Exit; } } // if (*pfmtid != pfo->fmtid)
} // else if (pfmtid != NULL)
_oSection = pfo->dwOffset; _cSection = _pph->reserved;
} // if (_HasPropHeader())
psh = _GetSectionHeader();
if (cbstm < _oSection + CB_PROPERTYSECTIONHEADER || psh->cbSection < CB_PROPERTYSECTIONHEADER + psh->cProperties * CB_PROPERTYIDOFFSET || cbstm < _oSection + CB_PROPERTYSECTIONHEADER + psh->cProperties * CB_PROPERTYIDOFFSET || cbstm < _oSection + psh->cbSection) { _cSection = 0; DebugTrace(0, Dbg, ("_LoadHeader: too small for section\n")); goto Exit; }
if (_HasPropHeader()) { // Scan the property set for a code page, and set _CodePage.
_SearchForCodePage( pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// If we have multiple sections, record the tail length
// (the size of the property set beyond this section).
if (_cSection > 1) { _State |= CPSS_MULTIPLESECTIONS; _cbTail = cbMin - (_oSection + psh->cbSection); DebugTrace(0, Dbg, ("_LoadHeader: cbTail=%x\n", _cbTail)); }
// Fix all the problems we know how to fix in the in-memory
// representation of the property set.
if (fSummaryInformation || (_State & CPSS_DOCUMENTSUMMARYINFO)) { if (fSummaryInformation) { _FixSummaryInformation(&cbstm, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; }
_FixPackedPropertySet( pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; }
if (Mode == CREATEPROP_DELETE) { loadstate = LOADSTATE_USERDEFINEDDELETE; goto Exit; } }
// ----
// Exit
// ----
loadstate = LOADSTATE_DONE;
Exit:
return( loadstate ); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_FixSummaryInformation
//
// Synopsis: Fix up the memory image of a SummaryInformation propset,
// except for packing or padding problems (which are fixed
// in _FixPackedPropertySet).
//
// Arguments: [pcbstm] - The size of the mapped stream. This may
// be updated by this routine.
// [pstatus] - Pointer to NTSTATUS code.
//
// Returns: None
//---------------------------------------------------------------------------
#define PID_THUMBNAIL 0x00000011 // SummaryInformation thumbnail property
VOID CPropertySetStream::_FixSummaryInformation(IN OUT ULONG *pcbstm, OUT NTSTATUS *pstatus) { PROPERTYSECTIONHEADER *psh; PROPERTYIDOFFSET *ppo, *ppoMax;
*pstatus = STATUS_SUCCESS;
psh = _LoadPropertyOffsetPointers(&ppo, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// Look for the MS Publisher problem. Pub only writes
// a Thumbnail, but it sets the section size too short (by 4 bytes).
// Pub95 has the additional problem that it doesn't DWORD-align the
// section and stream size. We fix both of these problems below.
// Skip all of this processing unless it looks like the Publisher problem.
if (psh != NULL && *pcbstm == _oSection + psh->cbSection + sizeof(ULONG)) { // Look for the thumbnail property.
for ( ; ppo < ppoMax; ppo++) { if (ppo->propid == PID_THUMBNAIL) { SERIALIZEDPROPERTYVALUE const *pprop;
// If this property isn't properly aligned, then ignore it.
if (ppo->dwOffset & (sizeof(DWORD) - 1)) { break; }
// Get a pointer to the property.
pprop = (SERIALIZEDPROPERTYVALUE *) _MapOffsetToAddress(ppo->dwOffset);
// Look specifically for the Publisher's Thumbnail property.
// If this is a Publisher set, the lengths won't add
// up correctly. For the lengths to add up correctly,
// the offset of the property, plus
// the length of the thumbnail, plus the size of the VT
// DWORD and the size of the length DWORD should be the
// size of the Section. But In the case of Publisher,
// the section length is 4 bytes short.
if (PropByteSwap(pprop->dwType) == VT_CF // It's in a clipboard format
&& // For Windows
*(ULONG *) &pprop->rgb[sizeof(ULONG)] == PropByteSwap((ULONG)MAXULONG) && ppo->dwOffset + // And the lengths don't add up
PropByteSwap( *(ULONG *) pprop->rgb ) + (3 - 2) * sizeof(ULONG) == psh->cbSection) { // We've found the Publisher problem.
// For Pub95 files, we must dword-align the section
// and stream size. We don't change the size of the underlying
// stream, however, just the mapping. This is because if the caller
// doesn't make any explicit changes, we don't want the mapped Stream
// to be modified. We do this step before fixing the section-size
// problem, so if it should fail we haven't touched anything.
if( !IsDwordAligned( *pcbstm )) { // Increase the size of the buffer, and reload the
// psh pointer.
*pcbstm += DwordRemain(*pcbstm); _MSTM(SetSize)(*pcbstm, // The new size
FALSE, // Don't update the underlying stream
(VOID **) &_pph, // The new mapping
pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
psh = _LoadPropertyOffsetPointers(&ppo, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// Align the section size.
psh->cbSection += DwordRemain(psh->cbSection); }
// Now correct the section size.
DebugTrace(0, DEBTRACE_PROPPATCH, ( "_FixSummaryInformation: Patch section size: %x->%x\n", psh->cbSection, psh->cbSection + sizeof(ULONG)));
psh->cbSection += sizeof(ULONG);
} // if (pprop->dwType == VT_CF ...
break;
} // if (ppo->propid == PID_THUMBNAIL)
} // for ( ; ppo < ppoMax; ppo++)
} // if (psh != NULL && cbstm == _oSection + psh->cbSection + sizeof(ULONG))
// ----
// Exit
// ----
Exit:
return; }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_FixPackedPropertySet
//
// Synopsis: Align the memory image of a propset.
//
// Algorithm: We need to move the properties within the
// property set so that they are properly aligned,
// and we need to adjust the PID/Offset array accordingly.
// This is complicated by the fact that we may have to
// grow some propertes (which are not properly padded
// for alignement) and at the same time we may have to
// shrink some properties (which are over-padded).
//
// To handle these two constraints, and to
// avoid growing the underlying stream any more
// than necessary, we process the property set in
// two phases. In the Compaction phase, we shrink
// properties which are over-padded. In the Expansion
// phase, we grow properties which are under-padded.
// For example, say we have a property set with 3
// properties, all of which should be 4 bytes. But
// say they are currently 2, 4, and 6 bytes. Thus
// we must grow the first property, hold the second
// constant, and shrink the third property. In this
// example, after the Compaction phase, the 3 properties
// will be 2, 4, and 4 bytes. After the Expansion phase,
// the properties will be 4, 4, and 4 bytes.
//
// To do all of this, we make a copy of the PID/Offset
// array (apoT) and sort it. We then proceed to make
// two arrays of just offsets (no PIDs) - aopropShrink
// and aopropFinal. aopropShrink holds the offset for
// each property after the Compaction phase. aopropFinal
// holds the offset for each property after the
// Expansion phase. (Note that each of these phases
// could be skipped if they aren't necessary.)
//
// Finally, we perform the Compaction and Expansion,
// using aopropShrink and aopropFinal, respectively,
// as our guide.
//
// Arguments: [pstatus] -- Pointer to NTSTATUS code.
//
// Returns: None
//---------------------------------------------------------------------------
INT __cdecl fnOffsetCompare(VOID const *ppo1, VOID const *ppo2);
// DocumentSummaryInformation special case properties (w/packed vector elements)
#define PID_HEADINGPAIR 0x0000000c // heading pair (VT_VECTOR | VT_VARIANT):
// {VT_LPSTR, VT_I4} pairs
#define PID_DOCPARTS 0x0000000d // docparts (VT_VECTOR | VT_LPSTR)
//#define PID_HLINKS 0x00000015 // hlinks vector
VOID CPropertySetStream::_FixPackedPropertySet(OUT NTSTATUS *pstatus) { // ------
// Locals
// ------
BOOLEAN fPacked = FALSE; BOOLEAN fDocSummaryInfo = FALSE; #if DBGPROP
BOOLEAN fExpandDocSummaryInfo = FALSE; #endif
PROPERTYSECTIONHEADER *psh = NULL; PROPERTYIDOFFSET *ppoT, *ppoTMax; PROPERTYIDOFFSET *ppo, *ppoBase, *ppoMax;
PROPERTYIDOFFSET *apoT = NULL;
ULONG *aopropShrink = NULL; ULONG *aopropFinal = NULL; ULONG cbprop; ULONG cCompact, cExpand; ULONG *poprop = NULL;
#if i386 == 0
SERIALIZEDPROPERTYVALUE *ppropbuf = NULL; #endif
ULONG cbtotal = 0; ULONG cbpropbuf; // -----
// Begin
// -----
*pstatus = STATUS_SUCCESS;
// Determine if this is the first section of the DocSumInfo
// property set.
if ((_State & (CPSS_USERDEFINEDPROPERTIES | CPSS_DOCUMENTSUMMARYINFO)) == CPSS_DOCUMENTSUMMARYINFO) { fDocSummaryInfo = TRUE; }
// Get pointers into this section's header.
psh = _LoadPropertyOffsetPointers(&ppoBase, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// We know it's packed if the section-length isn't aligned.
fPacked = !IsDwordAligned(psh->cbSection);
// If we don't already know it's packed, check each of the properties in
// the PID/Offset array to see if one is not properly aligned, if so we'll
// assume that it's packed. Also, if this is an Ansi DocSumInfo property set,
// (first section), we'll assume that the HeadingPair and DocParts properties
// are packed (vectors).
if (!fPacked && psh != NULL) { for (ppo = ppoBase; ppo < ppoMax; ppo++) { if ( !IsDwordAligned(ppo->dwOffset) || ( fDocSummaryInfo && _CodePage != CP_WINUNICODE && ( ppo->propid == PID_HEADINGPAIR || ppo->propid == PID_DOCPARTS ) ) ) { fPacked = TRUE; break; } } }
// ----------------------------------------------------
// Fix the properties if they are packed or if there is
// unnecessary padding.
// ----------------------------------------------------
// If we know there's a problem, set a _State flag
// now. If we can fix the problem below, we'll clear it.
// Otherwise, the rest of the Class will know that there's
// an unresolved problem.
if (fPacked) { DebugTrace(0, DEBTRACE_PROPPATCH, ( "_FixPackedPropertySet: packed properties\n")); _State |= CPSS_PACKEDPROPERTIES; }
// ---------------------------------------------------------
// Create apoT (a sorted array of PID/Offsets), aopropShrink
// (the offsets for the Compaction phase) and aopropFinal
// (the offsets for the Expansion phase).
// ---------------------------------------------------------
// Create a buffer for a temporary PID/Offset array.
apoT = newk(mtPropSetStream, NULL) PROPERTYIDOFFSET[psh->cProperties + 1]; if (apoT == NULL) { *pstatus = STATUS_NO_MEMORY; goto Exit; }
// Copy the PID/offset pairs from the property set to the
// temporary PID/Offset array.
RtlCopyMemory( apoT, psh->rgprop, psh->cProperties * CB_PROPERTYIDOFFSET);
// Mark the end of the temporary array.
ppoTMax = apoT + psh->cProperties; ppoTMax->propid = PID_ILLEGAL; ppoTMax->dwOffset = psh->cbSection;
// Sort the PID/Offset array by offset and check for overlapping values:
qsort(apoT, psh->cProperties, sizeof(apoT[0]), fnOffsetCompare);
// Create two arrays which will hold property offsets.
// aopropShrink holds the offsets for the Compaction phase where
// we shrink the property set. aopropFinal holds the offsets
// of the final property set, which will be achieved in the
// Expansion phase.
aopropShrink = newk(mtPropSetStream, NULL) ULONG[psh->cProperties + 1]; if (aopropShrink == NULL) { *pstatus = STATUS_NO_MEMORY; goto Exit; }
aopropFinal = newk(mtPropSetStream, NULL) ULONG[psh->cProperties + 1]; if (aopropFinal == NULL) { *pstatus = STATUS_NO_MEMORY; goto Exit; }
#if i386 == 0
// On non-x86 machines, we can't directly access unaligned
// properties. So, allocate enough (aligned) memory to hold
// the largest unaligned property. We'll copy properties here
// when we need to access them.
cbpropbuf = 0;
for (ppoT = apoT; ppoT < ppoTMax; ppoT++) { if (!IsDwordAligned(ppoT->dwOffset)) { cbprop = DwordAlign(ppoT[1].dwOffset - ppoT->dwOffset); if (cbpropbuf < cbprop) { cbpropbuf = cbprop; } } }
if (cbpropbuf != 0) { ppropbuf = (SERIALIZEDPROPERTYVALUE *) newk(mtPropSetStream, NULL) BYTE[cbpropbuf]; if (ppropbuf == NULL) { *pstatus = STATUS_NO_MEMORY; goto Exit; } } #endif // i386==0
// ----------------------------------------------
// Iterate through the properties, filling in the
// entries of aopropShrink and aopropFinal.
// ----------------------------------------------
// We'll also count the number of compacts and expands
// necessary.
aopropShrink[0] = aopropFinal[0] = apoT[0].dwOffset; PROPASSERT(IsDwordAligned(aopropShrink[0])); cExpand = 0; cCompact = 0;
for (ppoT = apoT; ppoT < ppoTMax; ppoT++) { SERIALIZEDPROPERTYVALUE *pprop; BOOLEAN fDocSumLengthComputed = FALSE; ULONG cbpropOriginal;
// How much space does the property take up in the current
// property set?
cbpropOriginal = cbprop = ppoT[1].dwOffset - ppoT->dwOffset; pprop = (SERIALIZEDPROPERTYVALUE *) _MapOffsetToAddress(ppoT->dwOffset);
#if i386 == 0
// If necessary, put this property into an aligned buffer.
if (!IsDwordAligned(ppoT->dwOffset)) { DebugTrace(0, Dbg, ( "_FixPackedPropertySet: unaligned pid=%x off=%x\n", ppoT->propid, ppoT->dwOffset)); PROPASSERT(DwordAlign(cbprop) <= cbpropbuf); RtlCopyMemory((VOID *) ppropbuf, pprop, cbprop); pprop = ppropbuf; } #endif
// Calculate the actual length of this property, including
// the necessary padding. This might be bigger than the
// property's current length (if the propset wasn't properly
// padded), and it might be smaller than the property's current
// length (if the propset was over-padded).
if (ppoT->propid == PID_DICTIONARY) { // Get the size of the dictionary.
cbprop = DwordAlign(_DictionaryLength( (DICTIONARY const *) pprop, cbprop, pstatus)); if( !NT_SUCCESS(*pstatus) ) goto Exit; } else { ULONG cbpropT;
// Ansi DocSumInfo property sets have two vector properties
// which are packed. If this is one of those properties,
// we won't fix it yet, but we'll compute the size required
// when the elements are un-packed.
if (fDocSummaryInfo && _CodePage != CP_WINUNICODE) { if (ppoT->propid == PID_HEADINGPAIR) { fDocSumLengthComputed = _FixHeadingPairVector( PATCHOP_COMPUTESIZE, pprop, &cbpropT); } else if (ppoT->propid == PID_DOCPARTS) { fDocSumLengthComputed = _FixDocPartsVector( PATCHOP_COMPUTESIZE, pprop, &cbpropT); } }
// If we computed a length above, use it, otherwise calculate
// the length using the standard rules (we've already checked
// for the special cases).
if (fDocSumLengthComputed) { cbprop = cbpropT; #if DBGPROP
fExpandDocSummaryInfo = TRUE; #endif
} else { cbprop = PropertyLength(pprop, DwordAlign(cbprop), 0, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; }
} // if (ppoT->propid == PID_DICTIONARY) ... else
PROPASSERT(IsDwordAligned(cbprop));
// Now that we know the actual cbprop, use it to update the
// *next* entry in the two arrays of correct offsets.
//
// We want aopropFinal to hold the final, correct offsets,
// so we'll use cbprop to calculate this array.
// But for aopropShrink, we only want it to differ from
// the original array (apoT) when a property is shrinking,
// so we'll use min(cbNew,cbOld) for this array.
poprop = &aopropShrink[ ppoT - apoT ]; // 1st do aopropShrink
poprop[1] = poprop[0] + min(cbprop, cbpropOriginal);
poprop = &aopropFinal[ ppoT - apoT ]; // 2nd do aopropFinal
poprop[1] = poprop[0] + cbprop;
DebugTrace(0, Dbg, ( "_FixPackedPropertySet: pid=%x off=%x->%x\n", ppoT->propid, ppoT->dwOffset, poprop[0], poprop[0] < ppoT->dwOffset? " (compact)" : poprop[0] > ppoT->dwOffset? " (expand)" : ""));
// Is this compaction or an expansion?
// If we computed the doc-sum length, we count it as
// an expansion, even if the total property size didn't change,
// because we need the expand the elements within the vector.
if (cbprop < cbpropOriginal) { cCompact++; } else if (cbprop > cbpropOriginal || fDocSumLengthComputed) { cExpand++; } } // for (ppoT = apoT; ppoT < ppoTMax; ppoT++)
// -------------------------------
// Compact/Expand the Property Set
// -------------------------------
// We've now generated the complete aopropShrink and aopropFinal
// offset arrays. Now, if necessary, let's expand and/or compact
// the property set to match these offsets.
if (cExpand || cCompact) { ULONG cbstm; LONG cbdelta;
cbstm = _oSection + psh->cbSection + _cbTail; cbdelta = aopropFinal[psh->cProperties] - psh->cbSection;
DebugTrace(0, Dbg, ( "_FixPackedPropertySet: cbstm=%x cbdelta=%x cexpand=%x ccompact=%x\n", cbstm, cbdelta, cExpand, cCompact));
// -----------------------------
// Grow the Stream if necessary.
// -----------------------------
if (cbdelta > 0) { DebugTrace(0, Dbg, ( "SetSize(%x) _FixPackedPropertySet grow %x bytes\n", cbstm + cbdelta, cbdelta)); // On the set-size, say that this is a non-persistent
// change, so that the underlying Stream isn't modified.
// At this point, we don't know if this change should remain
// permanent (if the caller closes without making any changes
// the file should remain un-changed).
_MSTM(SetSize)( cbstm + cbdelta, FALSE, // Not persistent
(VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// reload all pointers into mapped image:
psh = _LoadPropertyOffsetPointers(&ppoBase, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// If there's another section after this one, move it back
// to the end of the stream now, which will create room for
// our expansion.
if (_cbTail != 0) { VOID *pvSrc = _MapAbsOffsetToAddress(cbstm - _cbTail);
PropMoveMemory( "_FixPackedPropertySet(_cbTail:grow)", psh, Add2Ptr(pvSrc, cbdelta), pvSrc, _cbTail); } } // if (cbdelta > 0)
// This previous step (growing the Stream), was the last one which can
// fail. We're about to modify the actual property set (we've been
// working only with temporary buffers so far). So we're always guaranteed
// a good property set, or the original set, we'll never end up with a
// half-updated set.
// ----------------
// Compaction Phase
// ----------------
// Compact the property set if necessary. I.e., adjust
// the property set buffer so that it matches aopropShrink.
if (cCompact > 0) { // Start at the beginning and move each property up.
poprop = aopropShrink; for (ppoT = apoT; ppoT < ppoTMax; ppoT++, poprop++) { if (*poprop != ppoT->dwOffset) { PROPASSERT(*poprop < ppoT->dwOffset); PROPASSERT(poprop[1] > *poprop);
// We're compacting; the property should not grow!
PROPASSERT( poprop[1] - *poprop <= ppoT[1].dwOffset - ppoT->dwOffset);
PropMoveMemory( "_FixPackedPropertySet(compact)", psh, Add2Ptr(psh, *poprop), Add2Ptr(psh, ppoT->dwOffset), poprop[1] - *poprop); } } // for (ppoT = apoT; ppoT < ppoTMax; ppoT++, poprop++)
} // if (cCompact > 0)
// ---------------
// Expansion phase
// ---------------
// Recall that, whether or not we just did a compaction, aopropShrink
// holds the property set offsets as they currently exist in the
// property set.
if (cExpand > 0) { // Start at the end and move each property back.
// The 'poprop' gives us the final correct offset
// of the current property.
LONG lOffsetIndex; poprop = &aopropFinal[psh->cProperties - 1];
// Start at the second-to-last entry in the arrays of offsets
// (the last entry is an artificially added one to mark the end of the
// property set).
for (lOffsetIndex = ppoTMax - apoT - 1, ppoT = ppoTMax - 1; lOffsetIndex >=0; lOffsetIndex--, poprop--, ppoT--) { // Get a pointer to the final location of this
// property.
SERIALIZEDPROPERTYVALUE *pprop; pprop = (SERIALIZEDPROPERTYVALUE *) Add2Ptr(psh, *poprop);
if (*poprop != aopropShrink[ lOffsetIndex ]) { ULONG cbCopy, cbOld; PROPASSERT(*poprop > aopropShrink[ lOffsetIndex ]); PROPASSERT(poprop[1] > *poprop); PROPASSERT(aopropShrink[ lOffsetIndex+1 ] > aopropShrink[ lOffsetIndex ]);
// How many bytes should we copy? The minimum size of the property
// calculated using the old and new offsets.
cbCopy = poprop[1] - poprop[0]; cbOld = aopropShrink[ lOffsetIndex+1 ] - aopropShrink[ lOffsetIndex+0 ];
if (cbCopy > cbOld) { cbCopy = cbOld; }
// Copy the property from its old location
// (psh+aopropShrink[lOffsetIndex]) to its new location
// (pprop == psh+*poprop).
DebugTrace(0, DEBTRACE_PROPPATCH, ( "_FixPackedPropertySet:move pid=%x off=%x->%x " "cb=%x->%x cbCopy=%x z=%x @%x\n", ppoT->propid, ppoT->dwOffset, *poprop, cbOld, poprop[1] - *poprop, cbCopy, DwordRemain(cbCopy), _MapAddressToOffset(Add2Ptr(pprop, cbCopy))));
PropMoveMemory( "_FixPackedPropertySet(expand)", psh, pprop, Add2Ptr(psh, aopropShrink[ lOffsetIndex ]), cbCopy); RtlZeroMemory( Add2Ptr(pprop, cbCopy), DwordRemain(cbCopy));
} // if (*poprop != ppoT->dwOffset)
// If this is an older DocSumInfo property set,
// and this property is one of the vector values,
// we must expand the vector elements now that we've
// room for it.
if (fDocSummaryInfo && _CodePage != CP_WINUNICODE) { ULONG cbpropT;
if (ppoT->propid == PID_HEADINGPAIR) { _FixHeadingPairVector( PATCHOP_EXPAND, pprop, &cbpropT); } else if (ppoT->propid == PID_DOCPARTS) { _FixDocPartsVector( PATCHOP_EXPAND, pprop, &cbpropT); } } // if (fDocSummaryInfo)
} // for (ppoT = ppoTMax; --ppoT >= apoT; popropNew--)
} // if (cExpand != 0)
// ---------------------------------------------------------
// Patch the section size and the moved properties' offsets.
// ---------------------------------------------------------
DebugTrace(0, DEBTRACE_PROPPATCH, ( "_FixPackedPropertySet: Patch section size %x->%x\n", psh->cbSection, psh->cbSection + cbdelta));
psh->cbSection += cbdelta;
// Iterate through the original PID/Offset array to update the
// offsets.
for (ppo = ppoBase; ppo < ppoMax; ppo++) { // Search the temporary PID/Offset array (which has the updated
// offsets) for ppo->propid.
for (ppoT = apoT; ppoT < ppoTMax; ppoT++) { if (ppo->propid == ppoT->propid) { // We've found ppo->propid in the temporary PID/Offset
// array. Copy the offset value from the temporary array
// to the actual array in the property set.
PROPASSERT(ppo->dwOffset == ppoT->dwOffset); ppo->dwOffset = aopropFinal[ppoT - apoT]; #if DBGPROP
if (ppo->dwOffset != ppoT->dwOffset) { DebugTrace(0, DEBTRACE_PROPPATCH, ( "_FixPackedPropertySet: Patch propid %x" " offset=%x->%x\n", ppo->propid, ppoT->dwOffset, ppo->dwOffset)); } // if (ppo->dwOffset != ppoT->dwOffset)
#endif
break;
} // if (ppo->propid == ppoT->propid)
} // for (ppoT = apoT; ppoT < ppoTMax; ppoT++)
} // for (ppo = ppoBase; ppo < ppoMax; ppo++)
// ------------
// Fix the tail
// ------------
// If we have a tail, fix it's offset in the FmtID/Offset
// array. Also, if we've overall shrunk this section, bring
// the tail in accordingly.
if (_cbTail != 0) { if (cbdelta < 0) { VOID *pvSrc = _MapAbsOffsetToAddress(cbstm - _cbTail);
PropMoveMemory( "_FixPackedPropertySet(_cbTail:shrink)", psh, Add2Ptr(pvSrc, cbdelta), pvSrc, _cbTail); }
_PatchSectionOffsets(cbdelta);
} // if (_cbTail != 0)
// If we get to this point we've successfully un-packed (or
// un-over-padded) the property set, so we can clear the
// state flag.
_State &= ~CPSS_PACKEDPROPERTIES;
} // if (cExpand || cCompact)
// ----
// Exit
// ----
Exit:
delete [] apoT; delete [] aopropShrink; delete [] aopropFinal;
#if i386 == 0
delete [] (BYTE *) ppropbuf; #endif // i386
} // CPropertySetStream::_FixPackedPropertySet()
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_FixDocPartsVector
//
// Synopsis: Align the memory image of a DocParts vector
// The DocParts property is part of the DocSumInfo
// property set (first section). It is a vector
// of strings, and in Ansi property sets it's packed
// and must be un-packed.
//
// Arguments: [PatchOp] -- patch request
// [pprop] -- property value to be patched or sized
// [pcbprop] -- pointer to computed property length
//
// Returns: TRUE if property type and all elements meet expectations;
// FALSE on error
//
// Note: Operate on a DocumentSummaryInformation first section property,
// PID_DOCPARTS. This property is assumed to be an array of
// VT_LPSTRs.
//
// PATCHOP_COMPUTESIZE merely computes the size required to unpack
// the property, and must assume it is currently unaligned.
//
// PATCHOP_ALIGNLENGTHS patches all VT_LPSTR lengths to DWORD
// multiples, and may rely on the property already being aligned.
//
// PATCHOP_EXPAND expands the property from the Src to Dst buffer,
// moving elements to DWORD boundaries, and patching VT_LPSTR
// lengths to DWORD multiples. The Src buffer is assumed to be
// unaligned, and the Dst buffer is assumed to be properly sized.
//---------------------------------------------------------------------------
BOOLEAN CPropertySetStream::_FixDocPartsVector( IN PATCHOP PatchOp, IN OUT SERIALIZEDPROPERTYVALUE *pprop, OUT ULONG *pcbprop) { PROPASSERT( PatchOp == PATCHOP_COMPUTESIZE || PatchOp == PATCHOP_ALIGNLENGTHS || PatchOp == PATCHOP_EXPAND); PROPASSERT(pprop != NULL); PROPASSERT(pcbprop != NULL);
// If the property is a variant vector,
// it's in an ANSI property set, and
// there are an even number of elements, ...
if ( PropByteSwap(pprop->dwType) == (VT_VECTOR | VT_LPSTR) && _CodePage != CP_WINUNICODE) { ULONG cString; VOID *pv;
cString = PropByteSwap( *(DWORD *) pprop->rgb ); pv = Add2Ptr(pprop->rgb, sizeof(DWORD));
if (_FixDocPartsElements(PatchOp, cString, pv, pv, pcbprop)) { *pcbprop += CB_SERIALIZEDPROPERTYVALUE + sizeof(ULONG); return(TRUE); } } return(FALSE); // Not a recognizable DocParts vector
}
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_FixDocPartsElements
//
// Synopsis: Recursively align the memory image of DocParts elements
//
// Arguments: [PatchOp] -- patch request
// [cString] -- count of strings remaining in the vector
// [pvDst] -- aligned overlapping destination buffer
// [pvSrc] -- unaligned overlapping source buffer
// [pcbprop] -- pointer to computed property length
//
// Returns: TRUE if all remaining elements meet expectations;
// FALSE on error
//
// Note: The pvDst & pvSrc buffers must be in property-set
// byte order (little endian).
//---------------------------------------------------------------------------
BOOLEAN CPropertySetStream::_FixDocPartsElements( IN PATCHOP PatchOp, IN ULONG cString, OUT VOID *pvDst, IN VOID UNALIGNED const *pvSrc, OUT ULONG *pcbprop) { ULONG cb; PROPASSERT( PatchOp == PATCHOP_COMPUTESIZE || PatchOp == PATCHOP_ALIGNLENGTHS || PatchOp == PATCHOP_EXPAND); PROPASSERT(pvDst >= pvSrc); PROPASSERT(PatchOp != PATCHOP_ALIGNLENGTHS || pvDst == pvSrc);
if (cString == 0) { *pcbprop = 0; return(TRUE); } DWORD dwTemp; memcpy(&dwTemp, pvSrc, sizeof(DWORD)); cb = sizeof(DWORD) + PropByteSwap( dwTemp );
// If the caller serialized the vector properly, all we need to do is
// to round up the string lengths to DWORD multiples, so readers that
// treat these vectors as byte-aligned get faked out. We expect
// readers will not have problems with a DWORD aligned length, and a
// '\0' character a few bytes earlier than the length indicates.
if (PatchOp == PATCHOP_ALIGNLENGTHS) { cb = DwordAlign(cb); // Caller says it's already aligned
} if (_FixDocPartsElements( PatchOp, cString - 1, Add2Ptr(pvDst, DwordAlign(cb)), (VOID UNALIGNED const *) Add2ConstPtr(pvSrc, cb), pcbprop)) { *pcbprop += DwordAlign(cb);
if (PatchOp == PATCHOP_EXPAND) { PropMoveMemory( "_FixDocPartsElements", _GetSectionHeader(), pvDst, pvSrc, cb); RtlZeroMemory(Add2Ptr(pvDst, cb), DwordRemain(cb));
DebugTrace(0, DEBTRACE_PROPPATCH, ( "_FixDocPartsElements: Move(%x:%s) " "cb=%x->%x off=%x->%x z=%x @%x\n", cString, Add2Ptr(pvDst, sizeof(ULONG)), cb - sizeof(ULONG), DwordAlign(cb) - sizeof(ULONG), _MapAddressToOffset(pvSrc), _MapAddressToOffset(pvDst), DwordRemain(cb), _MapAddressToOffset(Add2Ptr(pvDst, cb)))); } if (PatchOp != PATCHOP_COMPUTESIZE) { PROPASSERT( PatchOp == PATCHOP_ALIGNLENGTHS || PatchOp == PATCHOP_EXPAND);
DebugTrace(0, DEBTRACE_PROPPATCH, ( "_FixDocPartsElements: Patch(%x:%s) cb=%x->%x\n", cString, Add2Ptr(pvDst, sizeof(ULONG)), *(ULONG *) pvDst, DwordAlign(*(ULONG *) pvDst)));
// put in the length
*(ULONG *) pvDst = PropByteSwap( (DWORD) DwordAlign( PropByteSwap(*(ULONG *) pvDst) ) ); } return(TRUE); } return(FALSE); // Not a recognizable DocParts vector
}
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_FixHeadingPairVector
//
// Synopsis: Align the memory image of a HeadingPair vector.
// The HeadingPair property is part of the DocSumInfo
// property set (first section). It's a vector of
// Variants, where the elements are alternating
// strings and I4s (the string is a heading name,
// and the I4 is the count of DocumentParts in that
// heading). In Ansi property sets, these elements
// are packed, and must be un-packed.
//
// Arguments: [PatchOp] -- patch request
// [pprop] -- property value to be patched or sized
// [pcbprop] -- pointer to computed property length
//
// Returns: TRUE if property and all elements meet expectations;
// FALSE on error
//
// Note: Operate on a DocumentSummaryInformation first section property,
// PID_HEADINGPAIR. This property is assumed to be an array of
// VT_VARIANTs with an even number of elements. Each pair must
// consist of a VT_LPSTR followed by a VT_I4.
//
// PATCHOP_COMPUTESIZE merely computes the size required to unpack
// the property, and must assume it is currently unaligned.
//
// PATCHOP_ALIGNLENGTHS patches all VT_LPSTR lengths to DWORD
// multiples, and may rely on the property already being aligned.
//
// PATCHOP_EXPAND expands the property from the Src to Dst buffer,
// moving elements to DWORD boundaries, and patching VT_LPSTR
// lengths to DWORD multiples. The Src buffer is assumed to be
// unaligned, and the Dst buffer is assumed to be properly sized.
//---------------------------------------------------------------------------
BOOLEAN CPropertySetStream::_FixHeadingPairVector( IN PATCHOP PatchOp, IN OUT SERIALIZEDPROPERTYVALUE *pprop, OUT ULONG *pcbprop) { ULONG celem; ULONG cbprop = 0;
PROPASSERT( PatchOp == PATCHOP_COMPUTESIZE || PatchOp == PATCHOP_ALIGNLENGTHS || PatchOp == PATCHOP_EXPAND); PROPASSERT(pprop != NULL); PROPASSERT(pcbprop != NULL);
// If the property is a variant vector, and
// there are an even number of elements, ...
if( PropByteSwap(pprop->dwType) == (VT_VECTOR | VT_VARIANT) && ( (celem = PropByteSwap(*(ULONG *) pprop->rgb) ) & 1) == 0 && _CodePage != CP_WINUNICODE) { pprop = (SERIALIZEDPROPERTYVALUE *) Add2Ptr(pprop->rgb, sizeof(ULONG));
if (_FixHeadingPairElements(PatchOp, celem/2, pprop, pprop, pcbprop)) { *pcbprop += CB_SERIALIZEDPROPERTYVALUE + sizeof(ULONG); return(TRUE); } } return(FALSE); // Not a recognizable HeadingPair vector
}
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_FixHeadingPairElements
//
// Synopsis: Recursively align the memory image of HeadingPair elements
//
// Arguments: [PatchOp] -- patch request
// [cPairs] -- count of heading pairs remaining
// [ppropDst] -- aligned overlapping destination buffer
// [ppropSrc] -- unaligned overlapping source buffer
// [pcbprop] -- pointer to computed property length
//
// Returns: TRUE if all remaining elements meet expectations;
// FALSE on error
//---------------------------------------------------------------------------
BOOLEAN CPropertySetStream::_FixHeadingPairElements( IN PATCHOP PatchOp, IN ULONG cPairs, OUT SERIALIZEDPROPERTYVALUE *ppropDst, IN SERIALIZEDPROPERTYVALUE UNALIGNED const *ppropSrc, OUT ULONG *pcbprop) { PROPASSERT( PatchOp == PATCHOP_COMPUTESIZE || PatchOp == PATCHOP_ALIGNLENGTHS || PatchOp == PATCHOP_EXPAND); PROPASSERT(ppropDst >= ppropSrc); PROPASSERT(PatchOp != PATCHOP_ALIGNLENGTHS || ppropDst == ppropSrc);
if (cPairs == 0) { *pcbprop = 0; return(TRUE); }
// If the first element of the pair is a VT_LPSTR, ...
if( PropByteSwap(SPV_GetType(ppropSrc)) == VT_LPSTR ) { ULONG cb;
// Compute size of the string element.
DWORD dwStrLen; memcpy(&dwStrLen, SPV_GetRgb(ppropSrc), sizeof(DWORD)); dwStrLen = PropByteSwap(dwStrLen); cb = CB_SERIALIZEDPROPERTYVALUE + sizeof(ULONG) + dwStrLen;
// If the caller serialized the vector properly, all we need to do is
// to round up the string lengths to DWORD multiples, so readers that
// treat these vectors as byte-aligned get faked out. We expect
// readers will not have problems with a DWORD aligned length, and a
// '\0' character a few bytes earlier than the length indicates.
if (PatchOp == PATCHOP_ALIGNLENGTHS) { cb = DwordAlign(cb); // Caller says it's already aligned
}
// and if the second element of the pair is a VT_I4, ...
if ( PropByteSwap( (DWORD) VT_I4 ) == SPV_GetType( (SERIALIZEDPROPERTYVALUE UNALIGNED const *) Add2ConstPtr(ppropSrc, cb) )) { cb += CB_SERIALIZEDPROPERTYVALUE + sizeof(DWORD);
if (_FixHeadingPairElements( PatchOp, cPairs - 1, (SERIALIZEDPROPERTYVALUE *) Add2Ptr(ppropDst, DwordAlign(cb)), (SERIALIZEDPROPERTYVALUE UNALIGNED const *) Add2ConstPtr(ppropSrc, cb), pcbprop)) { *pcbprop += DwordAlign(cb);
if (PatchOp == PATCHOP_EXPAND) { // Move the unaligned VT_I4 property back in memory to an
// aligned boundary, move the string back to a (possibly
// different) aligned boundary, zero the space in between
// the two and patch the string length to be a DWORD
// multiple to fake out code that expects vector elements
// to be byte aligned.
// Adjust byte count to include just the string element.
cb -= CB_SERIALIZEDPROPERTYVALUE + sizeof(ULONG);
// Move the VT_I4 element.
PropMoveMemory( "_FixHeadingPairElements:I4", _GetSectionHeader(), Add2Ptr(ppropDst, DwordAlign(cb)), Add2ConstPtr(ppropSrc, cb), CB_SERIALIZEDPROPERTYVALUE + sizeof(ULONG));
// Move the VT_LPSTR element.
PropMoveMemory( "_FixHeadingPairElements:LPSTR", _GetSectionHeader(), ppropDst, ppropSrc, cb);
// Zero the space in between.
RtlZeroMemory(Add2Ptr(ppropDst, cb), DwordRemain(cb));
DebugTrace(0, DEBTRACE_PROPPATCH, ( "_FixHeadingPairElements: Move(%x:%s) " "cb=%x->%x off=%x->%x z=%x @%x\n", cPairs, &ppropDst->rgb[sizeof(ULONG)], PropByteSwap( *(ULONG *) ppropDst->rgb ), DwordAlign(PropByteSwap( *(ULONG *) ppropDst->rgb )), _MapAddressToOffset(ppropSrc), _MapAddressToOffset(ppropDst), DwordRemain(cb), _MapAddressToOffset(Add2Ptr(ppropDst, cb)))); }
if (PatchOp != PATCHOP_COMPUTESIZE) { PROPASSERT( PatchOp == PATCHOP_ALIGNLENGTHS || PatchOp == PATCHOP_EXPAND); #ifdef DBGPROP
SERIALIZEDPROPERTYVALUE const *ppropT = (SERIALIZEDPROPERTYVALUE const *) Add2Ptr(ppropDst, DwordAlign(cb)); #endif
DebugTrace(0, DEBTRACE_PROPPATCH, ( "_FixHeadingPairElements: Patch(%x:%s) " "cb=%x->%x, vt=%x, %x\n", cPairs, &ppropDst->rgb[sizeof(ULONG)], PropByteSwap( *(ULONG *) ppropDst->rgb ), DwordAlign( PropByteSwap( *(ULONG *) ppropDst->rgb )), PropByteSwap( ppropT->dwType ), PropByteSwap( *(ULONG *) ppropT->rgb )));
// Patch the string length to be a DWORD multiple.
// and we have to put back the swapped length
*(ULONG *) ppropDst->rgb = PropByteSwap( (DWORD) DwordAlign( PropByteSwap( *(ULONG *) ppropDst->rgb ))); } return(TRUE); } } } return(FALSE); // Not a recognizable HeadingPair vector
}
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::QueryPropertySet
//
// Synopsis: Return the classid for the property set code
//
// Arguments: [pspss] -- pointer to buffer for output
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: None
//---------------------------------------------------------------------------
VOID CPropertySetStream::QueryPropertySet(OUT STATPROPSETSTG *pspss, OUT NTSTATUS *pstatus) const { *pstatus = STATUS_SUCCESS;
PROPASSERT(_IsMapped()); PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder);
if ((_State & CPSS_USERDEFINEDDELETED) || _cSection < 1) { StatusAccessDenied(pstatus, "QueryPropertySet: deleted or no section"); goto Exit; } _MSTM(QueryTimeStamps)( pspss, (BOOLEAN) ((_Flags & CREATEPROP_NONSIMPLE) != 0)); pspss->clsid = _pph->clsid; pspss->fmtid = _GetFormatidOffset( (_State & CPSS_USERDEFINEDPROPERTIES)? 1 : 0)->fmtid; pspss->grfFlags = _CodePage == CP_WINUNICODE? PROPSETFLAG_DEFAULT : PROPSETFLAG_ANSI;
// ----
// Exit
// ----
Exit:
return; }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::SetClassId
//
// Synopsis: Set the classid for the property set code
//
// Arguments: [pclsid] -- pointer to new ClassId
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: None
//---------------------------------------------------------------------------
VOID CPropertySetStream::SetClassId(IN GUID const *pclsid, OUT NTSTATUS *pstatus) { *pstatus = STATUS_SUCCESS;
PROPASSERT(_IsMapped()); PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder);
if (IsReadOnlyPropertySet(_Flags, _State)) { StatusAccessDenied(pstatus, "SetClassId: deleted or read-only"); goto Exit; }
_SetModified(); _pph->clsid = *pclsid;
// ----
// Exit
// ----
Exit:
return; }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::EnumeratePropids
//
// Synopsis: enumerates the property ids in a prop set
//
// Arguments: [pkey] -- pointer to bookmark (0 implies beginning)
// [pcprop] -- on input: size; on output: # of props returned.
// [apropids] -- output buffer
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: TRUE if more properties are available
//---------------------------------------------------------------------------
BOOLEAN CPropertySetStream::EnumeratePropids( IN OUT ULONG *pkey, IN OUT ULONG *pcprop, OPTIONAL OUT PROPID *apropids, OUT NTSTATUS *pstatus) { PROPERTYIDOFFSET *ppo, *ppoStart, *ppoMax; ULONG cprop = 0; BOOLEAN fMorePropids = FALSE; PROPID propidPrev = *pkey;
*pstatus = STATUS_SUCCESS;
PROPASSERT(_IsMapped()); PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder);
if (_State & CPSS_USERDEFINEDDELETED) { StatusAccessDenied(pstatus, "EnumeratePropids: deleted"); goto Exit; }
if (_LoadPropertyOffsetPointers(&ppoStart, &ppoMax, pstatus) == NULL) { if( !NT_SUCCESS(*pstatus) ) goto Exit; } else { if (propidPrev != 0) // if not first call, start w/last propid
{ for (ppo = ppoStart; ppo < ppoMax; ppo++) { if (ppo->propid == propidPrev) { ppoStart = ++ppo; break; } } } for (ppo = ppoStart; ppo < ppoMax; ppo++) { if (ppo->propid != PID_DICTIONARY && ppo->propid != PID_CODEPAGE && ppo->propid != PID_LOCALE) { if (cprop >= *pcprop) { fMorePropids = TRUE; break; } if (apropids != NULL) { apropids[cprop] = ppo->propid; } cprop++; propidPrev = ppo->propid; } } } *pkey = propidPrev; *pcprop = cprop;
// ----
// Exit
// ----
Exit:
return(fMorePropids); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_LoadPropertyOffsetPointers
//
// Synopsis: Load start and (past) end pointers to PROPERTYIDOFFSET array
//
// Arguments: [pppo] -- pointer to base of PROPERTYIDOFFSET array
// [pppoMax] -- pointer past end of PROPERTYIDOFFSET array
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: Pointer to Section Header, NULL if section not present
// or if there was an error.
//---------------------------------------------------------------------------
PROPERTYSECTIONHEADER * CPropertySetStream::_LoadPropertyOffsetPointers( OUT PROPERTYIDOFFSET **pppo, OUT PROPERTYIDOFFSET **pppoMax, OUT NTSTATUS *pstatus) { PROPERTYSECTIONHEADER *psh; *pstatus = STATUS_SUCCESS;
PROPASSERT(_IsMapped());
if (_cSection != 0) { psh = _GetSectionHeader(); ULONG cbstm = _MSTM(GetSize)(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (cbstm < _oSection + CB_PROPERTYSECTIONHEADER || cbstm < _oSection + CB_PROPERTYSECTIONHEADER + psh->cProperties * CB_PROPERTYIDOFFSET || cbstm < _oSection + psh->cbSection) { StatusCorruption(pstatus, "LoadPropertyOffsetPointers: stream size"); goto Exit; } *pppo = psh->rgprop; *pppoMax = psh->rgprop + psh->cProperties; }
// ----
// Exit
// ----
Exit: if( !NT_SUCCESS(*pstatus) ) psh = NULL;
return(psh); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_LoadProperty
//
// Synopsis: return a pointer to the specified property value
//
// Arguments: [propid] -- property id for property
// [pcbprop] -- pointer to return property size, 0 on error
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: SERIALIZEDPROPERTYVALUE * -- NULL if not present
//---------------------------------------------------------------------------
SERIALIZEDPROPERTYVALUE * CPropertySetStream::_LoadProperty( IN PROPID propid, OUT OPTIONAL ULONG *pcbprop, OUT NTSTATUS *pstatus ) { PROPERTYSECTIONHEADER const *psh; PROPERTYIDOFFSET *ppo, *ppoBase, *ppoMax; SERIALIZEDPROPERTYVALUE *pprop = NULL;
*pstatus = STATUS_SUCCESS;
psh = _LoadPropertyOffsetPointers(&ppoBase, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (psh != NULL) { for (ppo = ppoBase; ppo < ppoMax; ppo++) { if (IsDwordAligned(ppo->dwOffset) && ppo->dwOffset >= CB_PROPERTYSECTIONHEADER + psh->cProperties * CB_PROPERTYIDOFFSET && psh->cbSection >= ppo->dwOffset + CB_SERIALIZEDPROPERTYVALUE) {
if (ppo->propid != propid) { continue; } pprop = (SERIALIZEDPROPERTYVALUE *) _MapOffsetToAddress(ppo->dwOffset);
if (pcbprop != NULL) { ULONG cb;
cb = psh->cbSection - ppo->dwOffset; if (propid == PID_DICTIONARY) { *pcbprop = _DictionaryLength( (DICTIONARY const *) pprop, cb, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; } else { *pcbprop = PropertyLength(pprop, cb, 0, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; } } if (pcbprop == NULL || psh->cbSection >= ppo->dwOffset + *pcbprop) { // Success
goto Exit; } }
pprop = NULL; StatusCorruption(pstatus, "LoadProperty: property offset"); goto Exit; } }
// ----
// Exit
// ----
Exit:
return(pprop); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::GetValue
//
// Synopsis: return a pointer to the specified property value
//
// Arguments: [propid] -- property id of property
// [pcbprop] -- pointer to returned property length
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: pointer to property value
//---------------------------------------------------------------------------
SERIALIZEDPROPERTYVALUE const * CPropertySetStream::GetValue( IN PROPID propid, OUT ULONG *pcbprop, OUT NTSTATUS *pstatus) { SERIALIZEDPROPERTYVALUE *pprop = NULL;
PROPASSERT(_IsMapped()); PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder);
if (_State & CPSS_USERDEFINEDDELETED) { StatusAccessDenied(pstatus, "GetValue: deleted"); goto Exit; } if (propid == PID_DICTIONARY) { DebugTrace(0, DEBTRACE_ERROR, ("GetValue: PID_DICTIONARY\n")); StatusInvalidParameter(pstatus, "GetValue: PID_DICTIONARY"); goto Exit; }
pprop = NULL; if (propid == PID_SECURITY || propid == PID_MODIFY_TIME) { SERIALIZEDPROPERTYVALUE aprop[2];
PROPASSERT(sizeof(aprop) >= sizeof(ULONG) + sizeof(LONGLONG));
aprop[0].dwType = PropByteSwap( (DWORD) VT_EMPTY ); if (propid == PID_SECURITY) { if (_MSTM(QuerySecurity)((ULONG *) aprop[0].rgb)) { aprop[0].dwType = PropByteSwap( (DWORD) VT_UI4 ); *pcbprop = 2 * sizeof(ULONG); } } else // (propid == PID_MODIFY_TIME)
{ LONGLONG ll;
if (_MSTM(QueryModifyTime)(&ll)) { PropByteSwap(&ll); memcpy(aprop[0].rgb, &ll, sizeof(LONGLONG)); aprop[0].dwType = PropByteSwap( (DWORD) VT_FILETIME ); *pcbprop = sizeof(ULONG) + sizeof(LONGLONG); } }
if( VT_EMPTY != PropByteSwap(aprop[0].dwType) ) { pprop = (SERIALIZEDPROPERTYVALUE *) newk(mtPropSetStream, NULL) BYTE[*pcbprop];
if (pprop == NULL) { StatusNoMemory(pstatus, "GetValue: no memory"); goto Exit; } DebugTrace(0, Dbg, ( "GetValue: pprop=%lx, vt=%lx, cb=%lx\n", pprop, PropByteSwap( aprop[0].dwType ), *pcbprop)); RtlCopyMemory(pprop, aprop, *pcbprop); } } // if (propid == PID_SECURITY || propid == PID_MODIFY_TIME)
else { pprop = _LoadProperty(propid, pcbprop, pstatus); if( !NT_SUCCESS(*pstatus) ) { pprop = NULL; goto Exit; } } // if (propid == PID_SECURITY || propid == PID_MODIFY_TIME) ... else
#if DBGPROP
if (pprop == NULL) { DebugTrace(0, Dbg, ("GetValue: propid=%lx pprop=NULL\n", propid)); } else { char valbuf[CB_VALUESTRING];
DebugTrace(0, Dbg, ( "GetValue: propid=%lx pprop=%l" szX " vt=%hx val=%s cb=%l" szX "\n", propid, _MapAddressToOffset(pprop), PropByteSwap( pprop->dwType ), ValueToString(pprop, *pcbprop, valbuf), *pcbprop)); } #endif
// ----
// Exit
// ----
Exit:
return(pprop); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::SetValue
//
// Synopsis: update/add/delete property values
//
// Arguments: [cprop] -- count of properties
// [avar] -- PROPVARIANT array
// [apinfo] -- PROPERTY_INFORMATION array
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: None
//
// Note: All the properties in the apinfo array can be classified into
// one of the following categories:
//
// PROPOP_IGNORE:
// No change. Deleting a non-existent property or the same
// propid appears later in the apinfo array.
//
// PROPOP_DELETE:
// Deletion of an existing property. Remove the
// PROPERTYIDOFFSET structure from the property offset array and
// and pack the remaining entries. Delete the property value
// and pack remaining property values
//
// PROPOP_INSERT:
// Addition of a new property. Insert the new PROPERTYIDOFFSET
// structure at the end of the property offset array. Insert
// the new property value at the end of the section/stream.
//
// PROPOP_MOVE:
// A property whose value needs to be updated out of place
// because of a change in the property value's size. A property
// value is moved to the end of the section if it grows or
// shrinks across a DWORD boundary. The existing value is
// removed from the section and the remaining values are packed.
// Then, the new value is inserted at the end of the section.
// The idea here is to move variable length properties that are
// being changed frequently as near as possible to the end of
// the stream to minimize the cost of maintaining a packed set
// of property values. Note that the property offset structure
// is not moved around in the array.
//
// PROPOP_UPDATE:
// A property whose value can be updated in-place. The property
// value's new size is equal to the old size. There are a
// number of variant types that take up a fixed amount of space,
// e.g., VT_I4, VT_R8 etc. This would also apply to any
// variable length property that is updated without changing
// the property value's size across a DWORD boundary.
//
// Note that while the property offset array is itself packed out
// of necessity (to conform to the spec), there may be unused
// entries at the end of the array that are not compressed out of
// the stream when properties are deleted. The unused space is
// detected and reused when new properties are added later.
//---------------------------------------------------------------------------
#define CCHUNKSTACK (sizeof(ascnkStack)/sizeof(ascnkStack[0]))
VOID CPropertySetStream::SetValue( IN ULONG cprop, IN PROPVARIANT const avar[], IN PROPERTY_INFORMATION *apinfo, OUT NTSTATUS *pstatus) { // ------
// Locals
// ------
CStreamChunk ascnkStack[6];
ULONG cpoReserve; ULONG cDelete, cInsert, cMove, cUpdate;
#if DBGPROP
ULONG cIgnore; char valbuf[CB_VALUESTRING]; KERNELSELECT( char valbuf2[CB_VALUESTRING], char varbuf[CB_VARIANT_TO_STRING]); #endif
ULONG iprop; ULONG cbstm; LONG cbChange, cbInsertMove; PROPERTYSECTIONHEADER *psh; CStreamChunk *pscnk0 = NULL; ULONG cbNewSize;
// ----------
// Initialize
// ----------
*pstatus = STATUS_SUCCESS;
PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder);
// Worst case, we will need chunks for:
// - the possible growth of the PROPERTYIDOFFSET array,
// - one for EACH property that is being modified,
// - and one chunk to mark the end of the property data.
CStreamChunkList scl( 1 + cprop + 1, 1 + cprop + 1 <= CCHUNKSTACK? ascnkStack : ((CStreamChunk*)NULL) );
PROPASSERT(_IsMapped());
// Validate that this property set can be written to.
if (IsReadOnlyPropertySet(_Flags, _State)) { StatusAccessDenied(pstatus, "SetValue: deleted or read-only"); goto Exit; }
// Mark the propset dirty.
_SetModified();
psh = _GetSectionHeader();
cpoReserve = 0; cDelete = cInsert = cMove = cUpdate = 0; #if DBGPROP
cIgnore = 0; #endif
cbInsertMove = cbChange = 0;
pscnk0 = scl.GetFreeChunk(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
pscnk0->oOld = 0; pscnk0->cbChange = 0; PROPASSERT(pscnk0 == scl.GetChunk(0));
cbstm = _oSection + psh->cbSection + _cbTail; PROPASSERT( cbstm <= _MSTM(GetSize)(pstatus) && NT_SUCCESS(*pstatus) ); PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail);
// ------------------------
// Classify all the updates
// ------------------------
// Each update gets classified as ignore, delete, insert, move,
// or update.
// Lookup the old value for each of the properties specified and
// compute the current size.
for (iprop = 0; iprop < cprop; iprop++) { ULONG i; ULONG cbPropOld; SERIALIZEDPROPERTYVALUE const *pprop = NULL;
PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail);
if (IsReadOnlyPropid(apinfo[iprop].pid)) { if (cprop != 1 || apinfo[0].pid != PID_DICTIONARY || apinfo[0].cbprop == 0 || ( avar == NULL || avar[0].vt != VT_DICTIONARY ) ) { DebugTrace(0, DEBTRACE_ERROR, ( "SetValue: read-only propid=%lx\n", apinfo[iprop].pid)); StatusInvalidParameter(pstatus, "SetValue: read-only PROPID"); goto Exit; } }
if (apinfo[iprop].pid != PID_ILLEGAL) { pprop = _LoadProperty(apinfo[iprop].pid, &cbPropOld, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail); }
// If this propid appears later in the array, ignore it.
for (i = iprop + 1; i < cprop; i++) { if (apinfo[i].pid == apinfo[iprop].pid) { #if DBGPROP
cIgnore++; #endif
apinfo[iprop].operation = PROPOP_IGNORE; break; } }
// If this propid appears only once or if it's the last instance,
// load the property and compute its size.
if (i == cprop) { VOID *pvStart = NULL;
PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail); if (pprop != NULL) { ULONG cbPropNew;
PROPASSERT(apinfo[iprop].pid != PID_DICTIONARY); if (apinfo[iprop].cbprop == 0) { DebugTrace(0, Dbg, ( "SetValue: Deleting propid=%lx oOld=%l" szX " vt=%hx val=%s cb=%l" szX "\n", apinfo[iprop].pid, _MapAddressToOffset(pprop), PropByteSwap( pprop->dwType ), ValueToString(pprop, cbPropOld, valbuf), cbPropOld));
cbPropNew = 0; cDelete++; apinfo[iprop].operation = PROPOP_DELETE; } else { DebugTrace(0, Dbg, ( "SetValue: Modifying propid=%lx oOld=%l" szX " vt=%hx-->%hx cb=%l" szX "-->%l" szX " val=%s-->%s\n", apinfo[iprop].pid, _MapAddressToOffset(pprop), PropByteSwap( pprop->dwType ), KERNELSELECT( PropByteSwap( apinfo[iprop].pprop->dwType ), avar[iprop].vt), cbPropOld, apinfo[iprop].cbprop, ValueToString(pprop, cbPropOld, valbuf), KERNELSELECT( ValueToString( apinfo[iprop].pprop, apinfo[iprop].cbprop, valbuf2), VariantToString( avar[iprop], varbuf, sizeof( varbuf )))));
cbPropNew = apinfo[iprop].cbprop; if (cbPropOld != cbPropNew) { cbInsertMove += apinfo[iprop].cbprop; cMove++; apinfo[iprop].operation = PROPOP_MOVE; } else { cUpdate++; apinfo[iprop].operation = PROPOP_UPDATE; } }
if (apinfo[iprop].operation != PROPOP_UPDATE) { // Update the list of chunks that need to be adjusted
CStreamChunk *pscnk = scl.GetFreeChunk(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
pscnk->oOld = _MapAddressToOffset(pprop); pscnk->cbChange = - (LONG) cbPropOld; }
// Stream size change
cbChange += cbPropNew - cbPropOld; }
// Delete non-existent property:
else if (apinfo[iprop].cbprop == 0) { #if DBGPROP
cIgnore++; #endif
PROPASSERT(apinfo[iprop].pid != PID_DICTIONARY); apinfo[iprop].operation = PROPOP_IGNORE; }
// Insert new property:
else { DebugTrace(0, Dbg, ( "SetValue: Inserting new propid=%lx vt=%hx " "cbNew=%l" szX " val=%s\n", apinfo[iprop].pid, KERNELSELECT( PropByteSwap( apinfo[iprop].pprop->dwType ), avar[iprop].vt), apinfo[iprop].cbprop, KERNELSELECT( ValueToString( apinfo[iprop].pprop, apinfo[iprop].cbprop, valbuf), VariantToString( avar[iprop], varbuf, sizeof( varbuf )))));
PROPASSERT(apinfo[iprop].pid != PID_ILLEGAL);
cbInsertMove += apinfo[iprop].cbprop; cbChange += apinfo[iprop].cbprop;
cInsert++; apinfo[iprop].operation = PROPOP_INSERT; } PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail); }
}
DebugTrace(0, Dbg, ("SetValue: Total Props %l" szX "\n", cprop)); DebugTrace(0, Dbg, ( "SetValue: Delete=%l" szX " Insert=%l" szX " Move=%l" szX " Update=%l" szX " Ignore=%l" szX "\n", cDelete, cInsert, cMove, cUpdate, cIgnore));
PROPASSERT(cDelete + cInsert + cMove + cUpdate + cIgnore == cprop); PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail);
// If we need to grow the property offset array, detect any unused
// entries at the end of the array that are available for reuse.
// and adjust the size difference to reflect the reuse.
if (cInsert > cDelete) { ULONG cpoReuse, cpoExpand;
cpoExpand = cInsert - cDelete; cpoReuse = _CountFreePropertyOffsets(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (cpoReuse > cpoExpand) { cpoReuse = cpoExpand; } cpoExpand -= cpoReuse;
// If adding a small number of new entries, but not reusing any old
// ones, add 10% more reserved entries (but only up to 10 more) to
// avoid having to continually grow the property offset array for
// clients that insist on adding a few properties at a time.
// We don't do this for the User-Defined property set, however,
// because older apps assume that the dictionary immediately follows
// the last entry in the PID/Offset array.
if (cpoExpand >= 1 && cpoExpand <= 2 && cpoReuse == 0 && !(_State & CPSS_USERDEFINEDPROPERTIES) ) { cpoReserve = 1 + min(psh->cProperties, 90)/10; cpoExpand += cpoReserve; } DebugTrace(0, Dbg, ( "SetValue: Reusing %l" szX " offsets, Expanding %l" szX " offsets\n", cpoReuse, cpoExpand));
pscnk0->oOld = CB_PROPERTYSECTIONHEADER + (psh->cProperties + cpoReuse) * CB_PROPERTYIDOFFSET; pscnk0->cbChange = cpoExpand * CB_PROPERTYIDOFFSET; cbChange += cpoExpand * CB_PROPERTYIDOFFSET; PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail);
} // if (cInsert > cDelete)
// Do we instead need to *shrink* the PID/Offset array?
// If so, don't shrink any more than necessary. We'll
// leave up to min(10%,10) blank entries.
// Also, if this is the User-Defined property set,
// there can never be any unused entries (for compatibility
// with older apps), so we do a complete shrink.
else if (cInsert < cDelete) { ULONG cpoRemove = 0; ULONG cpoDelta = cDelete - cInsert;
// How many blank entries do we already have?
ULONG cpoCurBlankEntries = _CountFreePropertyOffsets( pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if( _State & CPSS_USERDEFINEDPROPERTIES ) { cpoRemove = cpoDelta; } else { // How many blank entries can we have?
ULONG cpoMaxBlankEntries; cpoMaxBlankEntries = 1 + min(psh->cProperties - cpoDelta, 90)/10;
// If, after deleting the properties, we'd have too many,
// remove only enough to get us down to the max allowable.
if( cpoCurBlankEntries + cpoDelta > cpoMaxBlankEntries ) { cpoRemove = cpoCurBlankEntries + cpoDelta - cpoMaxBlankEntries; } } // if( _State & CPSS_USERDEFINEDPROPERTIES )
// Should we remove any PID/Offset entries?
if( cpoRemove > 0 ) { // Start removing at cpoRemove entries from the end of the PID/Offset array
pscnk0->oOld = CB_PROPERTYSECTIONHEADER + (psh->cProperties + cpoCurBlankEntries - cpoRemove) * CB_PROPERTYIDOFFSET;
// Remove the bytes of the cpoRemove entries.
pscnk0->cbChange = - (LONG) (cpoRemove * CB_PROPERTYIDOFFSET );
// Adjust the size of the section equivalently.
cbChange += pscnk0->cbChange; }
} // else if (cInsert < cDelete)
PROPASSERT( cbstm + cbChange >= _oSection + CB_PROPERTYSECTIONHEADER + (psh->cProperties + cInsert - cDelete) * CB_PROPERTYIDOFFSET + _cbTail);
// If we need to grow the stream, do it now.
if (cbChange > 0) { if (cbstm + cbChange > CBMAXPROPSETSTREAM) { StatusDiskFull(pstatus, "SetValue: 256k limit"); goto Exit; }
DebugTrace(0, Dbg, ( "SetSize(%x) SetValue grow\n", cbstm + cbChange));
_MSTM(SetSize)(cbstm + cbChange, TRUE, (VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// reload all pointers into mapped image:
psh = _GetSectionHeader(); PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail);
// If there's another section after this one, move it back to the
// end of the stream now.
if (_cbTail != 0) { VOID *pvSrc = _MapAbsOffsetToAddress(cbstm - _cbTail);
PropMoveMemory( "SetValue(_cbTail:grow)", psh, Add2Ptr(pvSrc, cbChange), pvSrc, _cbTail); } }
// From this point on, the operation should succeed.
// If necessary, the stream has already been grown.
if (cDelete + cInsert + cMove != 0) { // Delete and compact property offsets in the section header.
if (cDelete + cMove != 0) { _DeleteMovePropertyOffsets(apinfo, cprop, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; psh->cProperties -= cDelete; } PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail);
// Use the last chunk to mark the section end, and sort the chunks
// in ascending order by start offset.
CStreamChunk *pscnk = scl.GetFreeChunk(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
pscnk->oOld = psh->cbSection; pscnk->cbChange = 0;
scl.SortByStartAddress();
// If we're reducing the number of properties, we may be shrinking
// the PID/Offset array. So, update that array now, since
// we may remove some bytes at the end of it when we compact
// the stream.
if( cDelete > cInsert ) { _UpdatePropertyOffsets( &scl, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; }
// Compact the Stream following the directions in the
// chunk list.
_CompactStream(&scl);
// If the number of properties is holding constant or increasing,
// we can update the PID/Offset array now (because _CompactStream
// allocated any necessary space for us).
if( cDelete <= cInsert ) { _UpdatePropertyOffsets( &scl, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; }
// Set the new section size to include the deleted and inserted
// property offsets, and the deleted property values.
psh->cbSection += cbChange;
// Insert new property offsets at the end of the array.
if (cInsert + cMove != 0) { _InsertMovePropertyOffsets( apinfo, cprop, psh->cbSection - cbInsertMove, cpoReserve, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
psh->cProperties += cInsert; }
PROPASSERT(cbstm + cbChange == _oSection + psh->cbSection + _cbTail); if (_cbTail != 0) { // There's another section after this one; if we're shrinking
// the stream, move it up to the new end of the stream now.
if (cbChange < 0) { VOID *pvSrc = _MapAbsOffsetToAddress(cbstm - _cbTail);
PropMoveMemory( "SetValue(_cbTail:shrink)", psh, Add2Ptr(pvSrc, cbChange), pvSrc, _cbTail); } _PatchSectionOffsets(cbChange); } } // if (cDelete + cInsert + cMove != 0)
// Copy the new values.
// NOTE: It might seem unnecessary to delay the in-place updates until
// this for loop. We do not perform the in-place updates while
// classifying the changes because unmapping, remapping and changing
// the size required for handling other updates can fail. In the event
// of such a failure, the update would not be atomic. By delaying the
// in-place updates, we provide some degree of atomicity.
if (cInsert + cUpdate + cMove != 0) { BOOLEAN fDocSummaryInfo = FALSE;
if ((_State & (CPSS_USERDEFINEDPROPERTIES | CPSS_DOCUMENTSUMMARYINFO)) == CPSS_DOCUMENTSUMMARYINFO) { fDocSummaryInfo = TRUE; } for (iprop = 0; iprop < cprop; iprop++) { // Find property in the offset array and copy in the new value.
if (apinfo[iprop].operation == PROPOP_INSERT || apinfo[iprop].operation == PROPOP_UPDATE || apinfo[iprop].operation == PROPOP_MOVE) { SERIALIZEDPROPERTYVALUE *pprop=NULL; ULONG cbprop; PROPID propid = apinfo[iprop].pid;
pprop = _LoadProperty(propid, NULL, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; PROPASSERT(pprop != NULL);
// Special case for SetPropertyNames dictionary creation:
if (propid == PID_DICTIONARY) { PROPASSERT(CB_SERIALIZEDPROPERTYVALUE == CB_DICTIONARY); PROPASSERT(apinfo[iprop].cbprop == CB_SERIALIZEDPROPERTYVALUE); PROPASSERT(avar[iprop].vt == VT_DICTIONARY); ((DICTIONARY *) pprop)->cEntries = 0; } // if (propid == PID_DICTIONARY)
else {
// In User, serialize the PROPVARIANT in avar
// directly into the mapped stream.
cbprop = apinfo[iprop].cbprop; pprop = RtlConvertVariantToProperty( &avar[iprop], _CodePage, pprop, &cbprop, apinfo[iprop].pid, FALSE, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit;
PROPASSERT(pprop != NULL); PROPASSERT(cbprop == DwordAlign(cbprop)); PROPASSERT(cbprop == apinfo[iprop].cbprop);
// If writing a DocumentSummaryInformation property
// for which an alignment hack is provided, hack it now.
if (fDocSummaryInfo && _CodePage != CP_WINUNICODE) { // The two vectors in the DocSumInfo property set
// (if Ansi) are un-packed, but we'll adjust the lengths
// so that if a propset reader expects them to be packed,
// it will still work. E.g., a one character string will
// have a length of 4, with padding of NULL characters.
ULONG cbpropT;
if (propid == PID_HEADINGPAIR) { _FixHeadingPairVector( PATCHOP_ALIGNLENGTHS, pprop, &cbpropT); } else if (propid == PID_DOCPARTS) { _FixDocPartsVector( PATCHOP_ALIGNLENGTHS, pprop, &cbpropT); } } DebugTrace(0, Dbg, ( "SetValue:Insert: pph=%x pprop=%x cb=%3l" szX " vt=%4x val=%s o=%x oEnd=%x\n", _pph, pprop, apinfo[iprop].cbprop, PropByteSwap(pprop->dwType), ValueToString(pprop, apinfo[iprop].cbprop, valbuf), _MapAddressToOffset(pprop), _MapAddressToOffset(pprop) + apinfo[iprop].cbprop));
} // if (propid == PID_DICTIONARY) ... else
} // if (apinfo[iprop].operation == PROPOP_INSERT || ...
} // for (iprop = 0; iprop < cprop; iprop++)
} // if (cInsert + cUpdate + cMove != 0)
// If we need to shrink the stream or if we are cleaning up after a
// previous shrink that failed, do it last.
cbNewSize = _MSTM(GetSize)(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (cbNewSize != cbstm + cbChange) { DebugTrace(0, Dbg, ( "SetSize(%x) SetValue shrink\n", cbstm + cbChange)); _MSTM(SetSize)(cbstm + cbChange, TRUE, (VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; }
// ----
// Exit
// ----
Exit:
scl.Delete();
}
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_CountFreePropertyOffsets, private
//
// Synopsis: counts available (free) property offsets at and of array
//
// Arguments: [pstatus] -- pointer to NTSTATUS code
//
// Returns: count of available property offsets at and of array
//+--------------------------------------------------------------------------
ULONG CPropertySetStream::_CountFreePropertyOffsets(OUT NTSTATUS *pstatus) { PROPERTYIDOFFSET *ppo, *ppoMax; PROPERTYSECTIONHEADER const *psh; ULONG oMin = MAXULONG; ULONG oEnd; ULONG cFree = 0;
psh = _LoadPropertyOffsetPointers(&ppo, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (psh != NULL) { for ( ; ppo < ppoMax; ppo++) { if (oMin > ppo->dwOffset) { oMin = ppo->dwOffset; } } } if (oMin == MAXULONG) { goto Exit; } PROPASSERT(psh != NULL); oEnd = CB_PROPERTYSECTIONHEADER + psh->cProperties * CB_PROPERTYIDOFFSET; PROPASSERT(oEnd <= oMin);
cFree = (oMin - oEnd)/CB_PROPERTYIDOFFSET;
Exit:
return( cFree ); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_DeleteMovePropertyOffsets, private
//
// Synopsis: updates the offsets following the changes to the stream
//
// Arguments: [apinfo] -- array of property information
// [cprop] -- number of properties
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: None
//+--------------------------------------------------------------------------
VOID CPropertySetStream::_DeleteMovePropertyOffsets( IN PROPERTY_INFORMATION const *apinfo, IN ULONG cprop, OUT NTSTATUS *pstatus) { ULONG i; ULONG cDelete; PROPERTYSECTIONHEADER const *psh; PROPERTYIDOFFSET *ppo, *ppoBase, *ppoMax;
psh = _LoadPropertyOffsetPointers(&ppoBase, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; PROPASSERT(psh != NULL);
// Remove the deleted properties
DebugTrace(0, Dbg, ("Marking deleted/moved property offsets\n")); cDelete = 0; for (i = 0; i < cprop; i++) { if (apinfo[i].operation == PROPOP_DELETE || apinfo[i].operation == PROPOP_MOVE) { for (ppo = ppoBase; ppo < ppoMax; ppo++) { if (ppo->propid == apinfo[i].pid) { DebugTrace(0, Dbg, ( "%sing propid=%lx oOld=%l" szX "\n", apinfo[i].operation == PROPOP_DELETE? "Delet" : "Mov", ppo->propid, ppo->dwOffset)); if (apinfo[i].operation == PROPOP_DELETE) { cDelete++; ppo->dwOffset = MAXULONG; } else { ppo->dwOffset = 0; } break; } } } }
// scan once and compact the property offset array.
if (cDelete > 0) { PROPERTYIDOFFSET *ppoDst = ppoBase;
DebugTrace(0, Dbg, ("Compacting %l" szX " deleted props\n", cDelete)); for (ppo = ppoBase; ppo < ppoMax; ppo++) { if (ppo->dwOffset != MAXULONG) { if (ppo > ppoDst) { *ppoDst = *ppo; } DebugTrace(0, Dbg, ( "%sing propid=%lx oOld=%l" szX "\n", ppo > ppoDst? "Compact" : "Preserv", ppo->propid, ppo->dwOffset)); ppoDst++; } } PROPASSERT(cDelete == (ULONG) (ppoMax - ppoDst)); DebugTrace(0, Dbg, ("Zeroing %l" szX " entries\n", cDelete)); RtlZeroMemory(ppoDst, (BYTE *) ppoMax - (BYTE *) ppoDst); }
// ----
// Exit
// ----
Exit:
return; }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_UpdatePropertyOffsets, private
//
// Synopsis: update property offsets in section header
//
// Arguments: [pscl] -- list of chunks in stream that were changed
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: None
//+--------------------------------------------------------------------------
VOID CPropertySetStream::_UpdatePropertyOffsets( IN CStreamChunkList const *pscl, OUT NTSTATUS *pstatus) { PROPERTYSECTIONHEADER const *psh; PROPERTYIDOFFSET *ppo, *ppoMax;
// Update the offsets for the existing properties.
DebugTrace(0, Dbg, ("Updating existing property offsets\n"));
psh = _LoadPropertyOffsetPointers(&ppo, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; PROPASSERT(psh != NULL);
for ( ; ppo < ppoMax; ppo++) { if (ppo->dwOffset != 0) { #if DBGPROP
ULONG oOld = ppo->dwOffset; #endif
ppo->dwOffset = _GetNewOffset(pscl, ppo->dwOffset);
DebugTrace(0, Dbg, ( "UpdatePropertyOffsets: propid=%lx offset=%l" szX "-->%l" szX"\n", ppo->propid, oOld, ppo->dwOffset)); } }
Exit:
return; }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_InsertMovePropertyOffsets, private
//
// Synopsis: updates the offsets following the changes to the stream
//
// Arguments: [apinfo] -- array of property information
// [cprop] -- number of properties
// [oInsert] -- offset in section for new properties
// [cpoReserve] -- newly reserved property offsets to zero
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: None
//+--------------------------------------------------------------------------
VOID CPropertySetStream::_InsertMovePropertyOffsets( IN PROPERTY_INFORMATION const *apinfo, IN ULONG cprop, IN ULONG oInsert, IN ULONG cpoReserve, OUT NTSTATUS *pstatus) { ULONG i; PROPERTYSECTIONHEADER const *psh; PROPERTYIDOFFSET *ppo, *ppoBase, *ppoMax;
*pstatus = STATUS_SUCCESS;
psh = _LoadPropertyOffsetPointers(&ppoBase, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; PROPASSERT(psh != NULL);
// Insert the new property offsets at the end.
DebugTrace(0, Dbg, ("Inserting/Moving/Zeroing property offsets\n"));
for (i = 0; i < cprop; i++) { if (apinfo[i].operation == PROPOP_INSERT) { ppo = ppoMax++; ppo->propid = apinfo[i].pid; } else if (apinfo[i].operation == PROPOP_MOVE) { for (ppo = ppoBase; ppo < ppoMax; ppo++) { if (ppo->propid == apinfo[i].pid) { PROPASSERT(ppo->dwOffset == 0); break; } } } else { continue; }
PROPASSERT(ppo->propid == apinfo[i].pid); ppo->dwOffset = oInsert; oInsert += apinfo[i].cbprop;
DebugTrace(0, Dbg, ( "%sing propid=%lx offset=%l" szX " size=%l" szX "\n", apinfo[i].operation == PROPOP_INSERT? "Insert" : "Mov", ppo->propid, ppo->dwOffset, apinfo[i].cbprop)); } DebugTrace(0, Dbg, ( "Zeroing %x property offsets o=%l" szX " size=%l" szX "\n", cpoReserve, _MapAddressToOffset(ppoMax), cpoReserve * CB_PROPERTYIDOFFSET)); RtlZeroMemory(ppoMax, cpoReserve * CB_PROPERTYIDOFFSET);
// ----
// Exit
// ----
Exit:
return; }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_CompactStream, private
//
// Synopsis: compact all of the property stream chunks
//
// Arguments: [pscl] -- list of chunks in stream that were changed
//
// Returns: None
//
// Note:
// Each chunk structure represents a contiguous range of the stream to be
// completely removed or added. A terminating chunk is appended to
// transparently mark the end of the data stream. The unmodified data
// after each chunk (except the last one) must be preserved and compacted
// as necessary. Chunk structures contain section-relative offsets.
//
// Invariants:
// - Only the first chunk can represent an insertion; subsequent chunks
// always represent deletions.
// - The first chunk can never cause a deletion, but it might not cause
// any change at all.
// - The last chunk is a dummy used to mark the end of the stream.
//
// Algorithm:
// In the optimal case without insertions, each chunk's trailing data can
// be moved ahead (compacted) individually in ascending chunk index order.
// If the first chunk represents an insertion, then some consecutive
// number of data blocks must be moved back (in *descending* chunk index
// order) to make room for the insertion.
//
// Walk the chunk array to find the first point where the accumulated size
// change is less than or equal to zero.
//
// After (possibly) compacting a single range in descending chunk index
// order, compact all remaining chunks in ascending chunk index order.
//
// Example: the first chunk inserts 18 bytes for new property offsets
// (apo'[]), and the second two delete 10 bytes each (chnk1 & chnk2).
// There are four chunks in the array, and three blocks of data to move.
//
// oOld cbChange | AccumulatedChange oNew
// chunk[0]: 38 +18 | +18 38 (apo'[])
// chunk[1]: 48 -10 | +8 50 (chnk1)
// chunk[2]: 6c -10 | -8 74 (chnk2)
// chunk[3]: 8c 0 | -8 84 (end)
//
// Data blocks are moved in the following sequence to avoid overlap:
// DstOff SrcOff cbMove | Chunk#
// 60 58 14 | 1 chnk1/data2: descending pass (Dst > Src)
// 50 38 10 | 0 apo'[]/data1: descending pass (Dst > Src)
// 74 7c 10 | 2 chnk2/data3: ascending pass (Dst < Src)
//
// SrcOff = oOld - min(cbChange, 0)
// DstOff = SrcOff + AccumulatedChange
// cbMove = chnk[i+1].oOld - SrcOff
//
// Before compacting:
// 0 38 48 58 6c 7c 8c
// | | | | | | |
// V V 10 V -10 V 14 V -10 V 10 V
// +----+-------+----+-------+-------+-------+----------+-------+-------+
// | ph | afo[] | sh | apo[] | data1 | chnk1 | data2 | chnk2 | data3 |
// +----+-------+----+-------+-------+-------+----------+-------+-------+
//
// After compacting:
// 0 38 50 60 74 84
// | | | | | |
// V V +18 V 10 V 14 V 10 V
// +----+-------+----+-------+-----------+-------+----------+-------+
// | ph | afo[] | sh | apo[] | apo'[] | data1 | data2 | data3 |
// +----+-------+----+-------+-----------+-------+----------+-------+
//+--------------------------------------------------------------------------
VOID CPropertySetStream::_CompactStream( IN CStreamChunkList const *pscl) { ULONG i, iMax, iAscend; LONG cbChangeTotal, cbChangeTotalAscend; CStreamChunk const *pscnk;
// Subtract one to avoid operating on the terminating chunk directly.
iMax = pscl->Count() - 1;
// If the first chunk does not indicate an insertion, the first for loop is
// exited with i == 0.
//
// If the first chunk represents an insertion, either i == iMax or i itself
// indexes the first chunk that can be compacted normally (in ascending
// chunk index order). In either case, we compact in descending chunk
// index order starting just below i.
DebugTrace(0, Dbg, ( "CompactStream: %l" szX " chunks @%lx\n", pscl->Count(), pscl->GetChunk(0)));
cbChangeTotal = 0; for (i = 0; i < iMax; i++) { pscnk = pscl->GetChunk(i); PROPASSERT(i == 0 || pscnk->cbChange < 0); if (cbChangeTotal + pscnk->cbChange <= 0) { break; } cbChangeTotal += pscnk->cbChange; } iAscend = i; // save ascending order start
cbChangeTotalAscend = cbChangeTotal;
DebugTrace(0, Dbg, ("CompactStream: iAscend=%l" szX "\n", iAscend));
// First compact range in descending chunk index order if necessary:
while (i-- > 0) { pscnk = pscl->GetChunk(i); PROPASSERT(i == 0 || pscnk->cbChange < 0);
DebugTrace(0, Dbg, ("CompactStream: descend: i=%l" szX "\n", i)); #if DBGPROP
pscl->AssertCbChangeTotal(pscnk, cbChangeTotal); #endif
_CompactChunk(pscnk, cbChangeTotal, pscl->GetChunk(i + 1)->oOld); cbChangeTotal -= pscnk->cbChange; }
// Compact any remaining chunks in ascending chunk index order.
cbChangeTotal = cbChangeTotalAscend; for (i = iAscend; i < iMax; i++) { pscnk = pscl->GetChunk(i); PROPASSERT(i == 0 || pscnk->cbChange < 0);
DebugTrace(0, Dbg, ("CompactStream: ascend: i=%l" szX "\n", i)); cbChangeTotal += pscnk->cbChange; #if DBGPROP
pscl->AssertCbChangeTotal(pscnk, cbChangeTotal); #endif
_CompactChunk(pscnk, cbChangeTotal, pscl->GetChunk(i + 1)->oOld); } }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_CompactChunk, private
//
// Synopsis: Compact the data block following one chunk
//
// Arguments: [pscnk] -- pointer to stream chunk
// [cbChangeTotal] -- Bias for this chunk
// [oOldNext] -- offset of next chunk
//
// Returns: None
//+--------------------------------------------------------------------------
VOID CPropertySetStream::_CompactChunk( IN CStreamChunk const *pscnk, IN LONG cbChangeTotal, IN ULONG oOldNext) { LONG cbDelta = cbChangeTotal + min(pscnk->cbChange, 0);
DebugTrace(0, Dbg, ( "CompactChunk(pscnk->oOld=%l" szX ", pscnk->cbChange=%s%l" szX "\n" " cbChangeTotal=%s%l" szX ", cbDelta=%s%l" szX ", oOldNext=%l" szX ")\n", pscnk->oOld, pscnk->cbChange < 0? "-" : "", pscnk->cbChange < 0? -pscnk->cbChange : pscnk->cbChange, cbChangeTotal < 0? "-" : "", cbChangeTotal < 0? -cbChangeTotal : cbChangeTotal, cbDelta < 0? "-" : "", cbDelta < 0? -cbDelta : cbDelta, oOldNext));
if (cbChangeTotal != 0) { ULONG oSrc; VOID const *pvSrc;
oSrc = pscnk->oOld - min(pscnk->cbChange, 0); pvSrc = _MapOffsetToAddress(oSrc); PropMoveMemory( "CompactChunk", _GetSectionHeader(), (VOID *) Add2ConstPtr(pvSrc, cbChangeTotal), pvSrc, oOldNext - oSrc); } }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_PatchSectionOffsets, private
//
// Synopsis: patch section offsets after moving data around
//
// Arguments: [cbChange] -- size delta
//
// Returns: none
//+--------------------------------------------------------------------------
VOID CPropertySetStream::_PatchSectionOffsets( LONG cbChange) { ULONG i;
for (i = 0; i < _cSection; i++) { FORMATIDOFFSET *pfo;
pfo = _GetFormatidOffset(i); if (pfo->dwOffset > _oSection) { DebugTrace(0, DEBTRACE_PROPPATCH, ( "PatchSectionOffsets(%x): %l" szX " + %l" szX " --> %l" szX "\n", i, pfo->dwOffset, cbChange, pfo->dwOffset + cbChange)); pfo->dwOffset += cbChange; } } }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_GetNewOffset, private
//
// Synopsis: gets the new address
//
// Arguments: [pscl] -- list of stream chunks that were changed
// [oOld] -- old offset
//
// Returns: new offset
//+--------------------------------------------------------------------------
ULONG CPropertySetStream::_GetNewOffset( IN CStreamChunkList const *pscl, IN ULONG oOld) const { // The Chunk list is sorted by start offsets. Locate the chunk to which
// the old offset belongs, then use the total change undergone by the chunk
// to compute the new offset.
ULONG i; ULONG iMax = pscl->Count(); LONG cbChangeTotal = 0;
for (i = 0; i < iMax; i++) { CStreamChunk const *pscnk = pscl->GetChunk(i); if (pscnk->oOld > oOld) { break; } cbChangeTotal += pscnk->cbChange; if (pscnk->oOld == oOld) { PROPASSERT(pscnk->cbChange >= 0); break; } } PROPASSERT(i < iMax); DebugTrace(0, Dbg, ( "GetNewOffset: %l" szX " + %l" szX " --> %l" szX "\n", oOld, cbChangeTotal, oOld + cbChangeTotal)); return(oOld + cbChangeTotal); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_ComputeMinimumSize, private
//
// Synopsis: computes the minimum possible size of a property set stream
//
// Arguments: [cbstm] -- actual stream size
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: computed highest offset in use
//+--------------------------------------------------------------------------
ULONG CPropertySetStream::_ComputeMinimumSize( IN ULONG cbstm, OUT NTSTATUS *pstatus) { ULONG oMax = 0; *pstatus = STATUS_SUCCESS;
// Don't assume *any* class variables except _pph are loaded yet!
if (_pph != NULL && cbstm != 0) { ULONG cbMin; ULONG i; ULONG cSection;
cSection = 1; cbMin = 0;
if (_HasPropHeader()) { cSection = _pph->reserved; cbMin = CB_PROPERTYSETHEADER + cSection * CB_FORMATIDOFFSET; } oMax = cbMin;
// Add the size of each section
for (i = 0; i < cSection; i++) { ULONG oSectionEnd;
PROPERTYSECTIONHEADER const *psh = _GetSectionHeader(i, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
cbMin += psh->cbSection; oSectionEnd = _MapAddressToAbsOffset(psh) + psh->cbSection; if (oMax < oSectionEnd) { oMax = oSectionEnd; } } PROPASSERT(oMax <= cbstm); PROPASSERT(cbMin <= oMax); }
// ----
// Exit
// ----
Exit:
// oMax may have been set before an error occurred.
// In this case, set it to zero.
if( !NT_SUCCESS(*pstatus) ) oMax = 0;
return(oMax); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_DictionaryLength
//
// Synopsis: compute length of property set dictionary
//
// Arguments: [pdy] -- pointer to dictionary
// [cbbuf] -- maximum length of accessible memory at pdy
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: Byte-granular count of bytes in dictionary
//+--------------------------------------------------------------------------
ULONG CPropertySetStream::_DictionaryLength( IN DICTIONARY const *pdy, IN ULONG cbbuf, OUT NTSTATUS *pstatus ) const { ENTRY UNALIGNED const *pent; ULONG cbDict = CB_DICTIONARY; ULONG i;
*pstatus = STATUS_SUCCESS;
for (i = 0, pent = &pdy->rgEntry[0]; i < PropByteSwap( pdy->cEntries ); i++, pent = _NextDictionaryEntry( pent )) { if (cbbuf < cbDict + CB_ENTRY || cbbuf < _DictionaryEntryLength( pent )) { StatusCorruption(pstatus, "_DictionaryLength: section size"); goto Exit; }
cbDict += _DictionaryEntryLength( pent ); }
// ----
// Exit
// ----
Exit:
return(cbDict); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_PropertyNameLength
//
// Synopsis: compute length (*byte* count) of a property name
//
// Arguments: [pvName] -- property name, in the codepage of
// the property set
// [pcbName] -- pointer to returned byte length of name
//
// Returns: TRUE if name length is valid; else FALSE
//
// Note: The OLE 2.0 format mandates that the null be included as part
// of the length of the name that is stored in the dictionary.
// If the propset uses the Unicode code page, names contain
// WCHARs, otherwise they contain CHARs. In either case, the
// length is a byte count that includes the L'\0' or '\0'.
//
// Also note that this routine does not concern itself with
// the byte-order of the name: for Ansi names, it's irrelevant;
// and for Unicode names, L'\0' == PropByteSwap(L'\0').
//
//+--------------------------------------------------------------------------
BOOLEAN CPropertySetStream::_PropertyNameLength( IN VOID const *pvName, OUT ULONG *pcbName) const { ULONG cch;
if (_CodePage == CP_WINUNICODE) { cch = Prop_wcslen((WCHAR const *) pvName) + 1; *pcbName = cch * sizeof(WCHAR); } else { *pcbName = cch = strlen((char const *) pvName) + 1; } return(cch > 1 && cch <= CCH_MAXPROPNAMESZ ); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_ComparePropertyNames
//
// Synopsis: Compare two property names.
//
// Pre-Conditions:
// The property names are in the codepage of the
// property set.
//
// Arguments: [pvName1] -- property name 1
// [pvName2] -- property name 2
// [fSameByteOrder]-- TRUE: names are both big- or little-endian
// FALSE: 2n\d name is wrong endian
// [cbName] -- byte count of name length
// (includes terminator)
//
// Returns: TRUE if names are equal
//+--------------------------------------------------------------------------
BOOLEAN CPropertySetStream::_ComparePropertyNames( IN VOID const *pvName1, IN VOID const *pvName2, IN BOOL fSameByteOrder, IN ULONG cbName) const { if (_CodePage == CP_WINUNICODE) { // On big-endian systems, when the second name
// is byte-swapped, we'll byte-swap it into a new
// buffer to use for the comparisson.
#ifdef BIGENDIAN
WCHAR awcByteSwap[ CCH_MAXPROPNAMESZ ]; if( !fSameByteOrder ) { ULONG ulIndex = 0; PROPASSERT( (WCHAR)'\0' == ByteSwap( (WCHAR) '\0'));
do { awcByteSwap[ ulIndex ] = ByteSwap( ((WCHAR*)pvName2)[ ulIndex ] ); } while (awcByteSwap[ulIndex++] != (WCHAR)'\0'); } #endif // BIGENDIAN
return(Prop_wcsnicmp( (WCHAR const *) pvName1, #ifdef BIGENDIAN
fSameByteOrder ? (WCHAR const *) pvName2 : awcByteSwap, #else
(WCHAR const * ) pvName2, #endif
cbName / sizeof(WCHAR) ) == 0);
} // if (_CodePage == CP_WINUNICODE)
else { return(_strnicmp( (char const *) pvName1, (char const *) pvName2, cbName) == 0); } // if (_CodePage == CP_WINUNICODE) ... else
}
//+---------------------------------------------------------------------------
// Function: CPropertySetStream::DuplicatePropertyName
//
// Synopsis: Duplicate an OLECHAR property name string
//
// Arguments: [poszName] -- input string
// [cbName] -- count of bytes in string (includes null)
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: pointer to new string
//---------------------------------------------------------------------------
OLECHAR * CPropertySetStream::DuplicatePropertyName( IN OLECHAR const *poszName, IN ULONG cbName, OUT NTSTATUS *pstatus) const { OLECHAR *poc = NULL; *pstatus = STATUS_SUCCESS;
PROPASSERT(cbName != 0); PROPASSERT(IsOLECHARString(poszName, cbName));
if (cbName != 0) { PROPASSERT((ocslen(poszName) + 1) * sizeof(OLECHAR) == cbName);
poc = (OLECHAR *) _pma->Allocate(cbName);
if (NULL == poc) { StatusNoMemory(pstatus, "DuplicatePropertyName: no memory"); goto Exit; } RtlCopyMemory(poc, poszName, cbName); }
// ----
// Exit
// ----
Exit:
return(poc); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::QueryPropid
//
// Synopsis: translate a property name to a property id using the
// dictionary on the property stream
//
// Arguments: [poszName] -- name of property
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: propid for property if found; PID_ILLEGAL if not found
//---------------------------------------------------------------------------
PROPID CPropertySetStream::QueryPropid( IN OLECHAR const *poszName, OUT NTSTATUS *pstatus ) { // ------
// Locals
// ------
ULONG cbname; DICTIONARY const *pdy; ENTRY UNALIGNED const *pent; ULONG cdye; ULONG cbDict; // BYTE granular size!
VOID const *pvName=NULL; PROPID propid = PID_ILLEGAL;
// ----------
// Initialize
// ----------
*pstatus = STATUS_SUCCESS;
PROPASSERT(_HasPropHeader()); PROPASSERT(_IsMapped()); PROPASSERT( IsOLECHARString( poszName, MAXULONG )); PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder);
// Make sure this isn't a UD propset which has been deleted.
if (_State & CPSS_USERDEFINEDDELETED) { StatusAccessDenied(pstatus, "QueryPropid: deleted"); goto Exit; }
// Put the name into pvName, converting it if
// necessary to the code-page of the property set.
pvName = poszName; if (_CodePage == CP_WINUNICODE // Property set is Unicode
&& !OLECHAR_IS_UNICODE ) // Name is in Ansi
{ // Convert the caller-provided name from the system
// Ansi codepage to Unicode.
ULONG cb = 0; pvName = NULL; _OLECHARToWideChar( poszName, (ULONG)-1, CP_ACP, (WCHAR**)&pvName, &cb, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// If necessary, swap the WCHARs of the Unicode string.
//PropByteSwap( (WCHAR*) pvName, cb, sizeof(WCHAR) );
}
else if (_CodePage != CP_WINUNICODE // Property set is Ansi
&& OLECHAR_IS_UNICODE ) // Name is in Unicode
{ // Convert the caller-provided name from Unicode
// to the propset's Ansi codepage.
ULONG cb = 0; pvName = NULL; _OLECHARToMultiByte( poszName, (ULONG)-1, _CodePage, (CHAR**)&pvName, &cb, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; }
// How long is this property name (in bytes)?
if (!_PropertyNameLength(pvName, &cbname)) { // The length is invalid.
StatusInvalidParameter(pstatus, "QueryPropid: name length"); goto Exit; }
// Get a pointer to the raw dictionary.
pdy = (DICTIONARY const *) _LoadProperty(PID_DICTIONARY, &cbDict, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// Is there a dictionary?
if (pdy != NULL) { // Yes - there is a dictionary.
PROPERTYSECTIONHEADER const *psh = _GetSectionHeader();
// Search the dictionary for an entry name matching
// pvName.
for (cdye = PropByteSwap(pdy->cEntries), pent = &pdy->rgEntry[0]; cdye > 0; cdye--, pent = _NextDictionaryEntry( pent )) { // Is the length of this dictionary entry valid?
if ( _MapAddressToOffset(pent) + _DictionaryEntryLength( pent ) > psh->cbSection ) { StatusCorruption(pstatus, "QueryPropid: section size"); goto Exit; }
// If the byte-length matches what we're looking for,
// and the names compare successfully, then we're done.
if ( CCh2CB(PropByteSwap( ENTRY_GetCch(pent) )) == cbname && _ComparePropertyNames(pvName, ENTRY_GetSz(pent), FALSE, // pvName, pent->sz could be dif Endians
cbname) ) { propid = PropByteSwap( ENTRY_GetPropid(pent) ); break; } } // for (cdye = PropByteSwap(pdy->cEntries), pent = &pdy->rgEntry[0]; ...
PROPASSERT(cdye > 0 || pent == Add2ConstPtr(pdy, cbDict));
} // if (pdy != NULL)
// ----
// Exit
// ----
Exit: // If we did an alloc on the name to munge it,
// delete that buffer now. We must cast pvName
// as a non-const in order for the compiler to accept
// the free call.
if( pvName != poszName ) _pma->Free( (VOID*) pvName ); return(propid); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::QueryPropertyNameBuf
//
// Synopsis: convert from a property id to a property name using the
// dictionary in the property set, and putting the result
// in a caller-provided buffer.
//
// Arguments: [propid] -- property id to look up
// [aocName] -- output buffer
// [pcbName] -- IN: length of aocName;
// OUT: actual length of name
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: TRUE if name is found in dictionary
//---------------------------------------------------------------------------
BOOLEAN CPropertySetStream::QueryPropertyNameBuf( IN PROPID propid, OUT OLECHAR *aocName, IN OUT ULONG *pcbName, OUT NTSTATUS *pstatus) { BOOL fFound = FALSE; DICTIONARY const *pdy; ULONG cbDict; // BYTE granular size!
*pstatus = STATUS_SUCCESS;
PROPASSERT(_IsMapped()); PROPASSERT(propid != PID_DICTIONARY); PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder); PROPASSERT(NULL != aocName);
// Ensure that this isn't an already-deleted UD propset.
if (_State & CPSS_USERDEFINEDDELETED) { StatusAccessDenied(pstatus, "QueryPropertyNameBuf: deleted"); goto Exit; }
// Get a pointer to the raw dictionary.
pdy = (DICTIONARY const *) _LoadProperty(PID_DICTIONARY, &cbDict, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// Is there a dictionary?
if (pdy != NULL) { // Yes - the dictionary was found.
ULONG cdye; ENTRY UNALIGNED const *pent; VOID const *pvDictEnd;
// Get pointers to the first and last+1 entries.
pent = pdy->rgEntry; pvDictEnd = Add2ConstPtr(pdy, cbDict);
// Scan through the dictionary, searching for 'propid'.
for (cdye = PropByteSwap(pdy->cEntries), pent = &pdy->rgEntry[0]; cdye > 0; cdye--, pent = _NextDictionaryEntry( pent )) { // Make sure this entry doesn't go off the end of the
// dictionary.
if (Add2ConstPtr(pent, _DictionaryEntryLength( pent )) > pvDictEnd) { StatusCorruption(pstatus, "QueryPropertyNameBuf: dictionary entry size"); goto Exit; }
// Is this the PID we're looking for?
if (PropByteSwap(ENTRY_GetPropid(pent)) == propid) { // Yes. Copy or convert the name into the caller's
// buffer.
// Is a Unicode to Ansi conversion required?
if (_CodePage == CP_WINUNICODE // Property set is Unicode
&& !OLECHAR_IS_UNICODE ) // Caller's buffer is Ansi
{ WCHAR *pwszName = (WCHAR*) ENTRY_GetSz(pent);
// If we're byte-swapping, alloc a new buffer, swap
// pwszName into it (getting the string into system-endian
// byte-order), and point pwszName to the result.
PBSInPlaceAlloc( &pwszName, NULL, pstatus ); if( !NT_SUCCESS( *pstatus )) goto Exit;
// Convert the Unicode string in the property set
// to the system default codepage.
_WideCharToOLECHAR( pwszName, (ULONG)-1, CP_ACP, &aocName, pcbName, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// If we allocated a buffer for byte-swapping,
// we don't need it any longer.
if( pwszName != (WCHAR*) ENTRY_GetSz(pent) ) delete pwszName; }
// Or is an Ansi to Unicode conversion required?
else if (_CodePage != CP_WINUNICODE // Property set is Ansi
&& OLECHAR_IS_UNICODE ) // Caller's buffer is Unicode
{ // Convert the Ansi property set name from the
// propset's codepage to Unicode.
_MultiByteToOLECHAR( (CHAR*) ENTRY_GetSz(pent), (ULONG)-1, _CodePage, &aocName, pcbName, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; }
// Otherwise, no conversion of the name is required
else { ULONG ulEntryLen= CCh2CB(PropByteSwap(ENTRY_GetCch(pent))); // Copy the name into the caller's buffer.
RtlCopyMemory(aocName, ENTRY_GetSz(pent), min(ulEntryLen, *pcbName));
// Swap the name to the correct endian
// (This will do nothing if OLECHARs are CHARs).
PBSBuffer( aocName, min( CCh2CB(PropByteSwap( ENTRY_GetCch(pent) )), *pcbName), sizeof(OLECHAR) );
// Tell the caller the actual size of the name.
*pcbName = ulEntryLen; }
PROPASSERT( NULL == aocName || IsOLECHARString( aocName, MAXULONG )); fFound = TRUE; break;
} // if (ENTRY_GetPropid(pent) == propid)
} // for (cdye = pdy->cEntries, pent = &pdy->rgEntry[0]; ...
PROPASSERT(fFound || pent == pvDictEnd);
} // if (pdy != NULL)
// ----
// Exit
// ----
Exit:
return( fFound ); }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::QueryPropertyNames
//
// Synopsis: query dictionary names for the passed property ids.
//
// Arguments: [cprop] -- count of name to propid mappings to change
// [apid] -- array of property ids
// [aposz] -- array of pointers to the new names
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: TRUE if the property exists.
//+--------------------------------------------------------------------------
BOOLEAN CPropertySetStream::QueryPropertyNames( IN ULONG cprop, IN PROPID const *apid, OUT OLECHAR *aposz[], OUT NTSTATUS *pstatus) { DICTIONARY const *pdy; ULONG cbDict; // BYTE granular size!
ULONG iprop; BOOLEAN fFound = FALSE;
*pstatus = STATUS_SUCCESS;
PROPASSERT(_HasPropHeader()); PROPASSERT(_IsMapped()); PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder);
// If this is an attempt to access a deleted UD
// propset, exit now.
if (_State & CPSS_USERDEFINEDDELETED) { StatusAccessDenied(pstatus, "QueryPropertyNames: deleted"); goto Exit; }
// Validate the input array of strings.
for (iprop = 0; iprop < cprop; iprop++) { PROPASSERT(aposz[iprop] == NULL); }
// Get a pointer to the beginning of the dictionary
pdy = (DICTIONARY const *) _LoadProperty(PID_DICTIONARY, &cbDict, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// Did we get a dictionary?
if (pdy != NULL) { // Yes, the dictionary exists.
ULONG i; ENTRY UNALIGNED const *pent;
// Iterate through each of the entries in the dictionary.
for (i = 0, pent = &pdy->rgEntry[0]; i < PropByteSwap( pdy->cEntries ); i++, pent = _NextDictionaryEntry( pent )) { // Scan the input array of PIDs to see if one matches
// this dictionary entry.
for (iprop = 0; iprop < cprop; iprop++) { if( PropByteSwap(ENTRY_GetPropid(pent)) == apid[iprop] ) { // We've found an entry in the dictionary
// that's in the input PID array. Put the property's
// name in the caller-provided array (aposz).
PROPASSERT(aposz[iprop] == NULL);
// Do we need to convert to Unicode?
if (_CodePage != CP_WINUNICODE // Ansi property set
&& OLECHAR_IS_UNICODE) // Unicode property names
{ ULONG cbName = 0; _MultiByteToOLECHAR( ENTRY_GetSz(pent), (ULONG)-1, _CodePage, &aposz[iprop], &cbName, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; }
// Or, do we need to convert to Ansi?
else if (_CodePage == CP_WINUNICODE // Unicode property set
&& !OLECHAR_IS_UNICODE) // Ansi property names
{ ULONG cbName = 0; WCHAR *pwszName = (WCHAR*) ENTRY_GetSz(pent);
// If necessary, swap the Unicode name in the dictionary,
// pointing pwszName to the new, byte-swapped, buffer.
PBSInPlaceAlloc( &pwszName, NULL, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// And convert to Ansi.
_WideCharToOLECHAR( pwszName, (ULONG)-1, CP_ACP, &aposz[iprop], &cbName, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// If we alloced a new buffer for byte-swapping,
// we can free it now.
if( pwszName != (WCHAR*) ENTRY_GetSz(pent) ) delete pwszName;
} // else if (_CodePage == CP_WINUNICODE ...
// Otherwise, both the propset & in-memory property names
// are both Unicode or both Ansi, so we can just do
// an alloc & copy.
else { aposz[iprop] = DuplicatePropertyName( (OLECHAR *) ENTRY_GetSz(pent), CCh2CB(PropByteSwap(ENTRY_GetCch(pent))), pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// If necessary, swap the in-memory copy.
PBSBuffer( (OLECHAR*) aposz[iprop], CCh2CB( PropByteSwap( pent->cch )), sizeof(OLECHAR) );
} // if (_CodePage != CP_WINUNICODE ... else if ... else
PROPASSERT( IsOLECHARString( aposz[iprop], MAXULONG ));
fFound = TRUE;
} // if (PropByteSwap(ENTRY_GetPropid(pent) == apid[iprop])
} // for (iprop = 0; iprop < cprop; iprop++)
} // for (i = 0, pent = &pdy->rgEntry[0];
PROPASSERT(pent == Add2ConstPtr(pdy, cbDict));
} // if (pdy != NULL)
// ----
// Exit
// ----
Exit:
// If the property name simply didn't exist, return
// a special success code.
if( !fFound && NT_SUCCESS(*pstatus) ) *pstatus = STATUS_BUFFER_ALL_ZEROS;
return( fFound );
} // CPropertySetStream::QueryPropertyNames
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::SetPropertyNames
//
// Synopsis: changes dictionary entry names associated with property ids.
//
// Arguments: [cprop] -- count of name to propid mappings to change
// [apid] -- array of property ids
// [aposz] -- array of pointers to the new names
// [pstatus] -- pointer to NTSTATUS code
//
// Returns: None
//
// Note: Attempting to set a property name for a property that does not
// exist in the property set is not an error.
//
// Attempting to set a property name or property id that would
// result in a duplicate name or property id causes the existing
// entry(ies) to be replaced.
//+--------------------------------------------------------------------------
VOID CPropertySetStream::SetPropertyNames( IN ULONG cprop, IN const PROPID *apid, IN OPTIONAL OLECHAR const * const aposz[], OUT NTSTATUS *pstatus ) {
// ------
// Locals
// ------
DICTIONARY *pdy = NULL; ULONG cbDictOld = 0; // Byte granular Old dictionary size
ULONG cbDictOldD = 0; // Dword granular Old dictionary size
ULONG iprop = 0; ULONG i = 0; ULONG cDel, cAdd; LONG cbDel, cbAdd; // Byte granular sizes
LONG cbChangeD; // Dword granular size
ENTRY UNALIGNED *pent; BOOLEAN fDupPropid = FALSE; BOOLEAN fDupName = FALSE; BOOLEAN fDeleteByName = FALSE; BOOLEAN fDeleteAll = FALSE; VOID **appvNames = NULL;
ULONG cbstm; ULONG oDictionary; ULONG cbTail; ULONG cbNewSize;
// ----------
// Initialize
// ----------
*pstatus = STATUS_SUCCESS;
DebugTrace(0, Dbg, ( "SetPropertyNames(cprop=%x, apid=%x, apwsz=%x)\n", cprop, apid, aposz));
PROPASSERT(_HasPropHeader()); PROPASSERT(_IsMapped()); PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder);
// --------
// Validate
// --------
// Verify that this propset is modifiable.
if (IsReadOnlyPropertySet(_Flags, _State)) { StatusAccessDenied(pstatus, "SetPropertyNames: deleted or read-only"); goto Exit; }
// Verify that none of the names are illegally long.
if (aposz != NULL) { for (iprop = 0; iprop < cprop; iprop++) { PROPASSERT( IsOLECHARString( aposz[iprop], MAXULONG ));
if (ocslen( aposz[iprop] ) > CCH_MAXPROPNAME) { StatusInvalidParameter(pstatus, "SetPropertyNames: Name is too long" ); goto Exit; } } } // if (apwsz != NULL)
// ----------------------------------------------------------------
// If necessary, convert each of the caller-provided names:
// to Unicode (if the property set is Unicode) or Ansi (otherwise).
// ----------------------------------------------------------------
// In the end, appvNames will have the names in the same codepage
// as the property set.
appvNames = (VOID **) aposz; if (appvNames != NULL) { // Do we need to convert the caller's names to Ansi?
if( _CodePage != CP_WINUNICODE // Property set is Ansi
&& OLECHAR_IS_UNICODE ) // Caller's names are Unicode
{ // Allocate an array of cprop string pointers.
appvNames = (VOID **) newk(mtPropSetStream, NULL) char *[cprop]; if (appvNames == NULL) { StatusNoMemory(pstatus, "SetpropertyNames: Ansi Name Pointers"); goto Exit; } RtlZeroMemory(appvNames, cprop * sizeof(appvNames[0]));
// Convert the caller-provided property names from Unicode to
// the property set's codepage.
for (iprop = 0; iprop < cprop; iprop++) { ULONG cb = 0; appvNames[iprop] = NULL; _OLECHARToMultiByte( (OLECHAR*) aposz[iprop], (ULONG)-1, _CodePage, (CHAR**) &appvNames[iprop], &cb, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; } } // if( _CodePage != CP_WINUNICODE ...
// Or, do we need to convert the caller's names to Unicode?
if( _CodePage == CP_WINUNICODE // Property set is Unicode
&& !OLECHAR_IS_UNICODE ) // Caller's names are Ansi
{ // Allocate an array of cprop string pointers.
appvNames = (VOID **) newk(mtPropSetStream, NULL) WCHAR *[cprop]; if (appvNames == NULL) { StatusNoMemory(pstatus, "SetpropertyNames: Unicode Name Pointers"); goto Exit; } RtlZeroMemory(appvNames, cprop * sizeof(appvNames[0]));
// Convert the caller-provided property names from the system
// default Ansi codepage to Unicode.
for (iprop = 0; iprop < cprop; iprop++) { ULONG cb = 0; appvNames[iprop] = NULL; _OLECHARToWideChar( (OLECHAR*) aposz[iprop], (ULONG)-1, CP_ACP, (WCHAR**) &appvNames[iprop], &cb, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; } } // if( _CodePage == CP_WINUNICODE )
} // if (appvNames != NULL)
// -----------------------------------------------------
// Compute total size of entries to be modified or added
// -----------------------------------------------------
cbAdd = 0; cAdd = 0; for (iprop = 0; iprop < cprop; iprop++) { // Did the caller give us no array of names? If so,
// it means that the name for this PID is to be deleted.
if (appvNames == NULL) { // If the PID is for the dictionary, then it must be the
// only entry in apid, and it indicates that we're going to
// delete all the names in the dictionary.
if (apid[iprop] == PID_DICTIONARY) { if (cprop != 1) { StatusInvalidParameter(pstatus, "SetPropertyNames: DeleteAll parms"); goto Exit; } fDeleteAll = TRUE; } }
// Otherwise, we're setting a new name for this PID.
else { ULONG cbname;
// Validate the caller-provided length.
if (!_PropertyNameLength(appvNames[iprop], &cbname)) { StatusInvalidParameter(pstatus, "SetPropertyNames: name length"); goto Exit; }
// See if this propid or name appears later in the array.
for (i = iprop + 1; i < cprop; i++) { ULONG cbname2;
if (apid[i] == apid[iprop]) { fDupPropid = TRUE; break; }
_PropertyNameLength(appvNames[i], &cbname2);
if (cbname == cbname2 && _ComparePropertyNames( appvNames[iprop], appvNames[i], TRUE, // Both names are in the same byte-order
cbname)) { fDupName = TRUE; break; } }
// If this propid appears only once or if it's the last instance,
// count it. If the property set is Unicode, include DWORD padding.
if (i == cprop) { DebugTrace(0, Dbg, ( _CodePage == CP_WINUNICODE? "Adding New Entry: propid=%lx L'%ws'\n" : "Adding New Entry: propid=%lx '%s'\n", apid[iprop], appvNames[iprop]));
cAdd++;
cbAdd += CB_ENTRY + cbname; if( _CodePage == CP_WINUNICODE ) { cbAdd = DwordAlign( cbAdd ); } } } } PROPASSERT( _CodePage == CP_WINUNICODE ? IsDwordAligned( cbAdd ) : TRUE );
// ---------------------------------------------
// Get the dictionary, creating it if necessary.
// ---------------------------------------------
_SetModified();
for (i = 0; ; i++) { PROPERTY_INFORMATION pinfo; PROPVARIANT var;
pdy = (DICTIONARY *) _LoadProperty(PID_DICTIONARY, &cbDictOld, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (pdy != NULL) { break; } PROPASSERT(i == 0); if (cprop == 0 || appvNames == NULL) { // no dictionary and we are deleting or doing nothing -- return
goto Exit; } // create dictionary if it doesn't exist
DebugTrace(0, Dbg, ("Creating empty dictionary\n"));
PROPASSERT(CB_SERIALIZEDPROPERTYVALUE == CB_DICTIONARY); pinfo.cbprop = CB_SERIALIZEDPROPERTYVALUE; pinfo.pid = PID_DICTIONARY;
var.vt = VT_DICTIONARY; SetValue(1, &var, &pinfo, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
Validate(pstatus); // Make sure dictionary was properly created
if( !NT_SUCCESS(*pstatus) ) goto Exit; DebugTrace(0, Dbg, ("Created empty dictionary\n"));
} // for (i = 0; ; i++)
// ----------------------------------------------------------------
// Compute total size of existing entries to be modified or deleted
// ----------------------------------------------------------------
// Walk the dictionary looking for entries which are referenced
// in the caller's 'apid' array or 'appvNames' array.
cbDel = 0; cDel = 0; for (i = 0, pent = &pdy->rgEntry[0]; i < PropByteSwap( pdy->cEntries ); i++, pent = _NextDictionaryEntry( pent )) { DebugTrace(0, Dbg, ( _CodePage == CP_WINUNICODE? "Dictionary Entry @%lx: propid=%lx L'%ws'\n" : "Dictionary Entry @%lx: propid=%lx '%s'\n", pent, PropByteSwap( ENTRY_GetPropid(pent) ), pent->sz ));
// For this dictionary entry, walk the caller's
// 'apid' and 'appvNames' arrays, looking for a match.
for (iprop = 0; iprop < cprop; iprop++) { // If we get to the bottom of this 'for' loop,
// then we know that we've found an entry to delete.
// If fDeleteAll, or the PID in apid matches this
// dictionary entry, then we can fall to the bottom.
// Otherwise, the following 'if' block checks the
// name in 'appvNames' against this dictionary entry.
if (!fDeleteAll && apid[iprop] != PropByteSwap( ENTRY_GetPropid(pent) )) { // The caller's PID didn't match this dictionary entry,
// does the name?
ULONG cbname;
// If we have no names from the caller, then we obviously
// don't have a match, and we can continue on to check this
// dictionary entry against the next of the caller's PIDs.
if (appvNames == NULL) { continue; }
// Or, if this name from the caller doesn't match this
// dictionary entry, we again can continue on to check
// the next of the caller's properties.
_PropertyNameLength(appvNames[iprop], &cbname); if (cbname != CCh2CB( PropByteSwap( ENTRY_GetCch(pent) )) || !_ComparePropertyNames( appvNames[iprop], ENTRY_GetSz(pent), FALSE, // appvNames & pent->sz may be dif endians.
cbname) ) { continue; } fDeleteByName = TRUE;
} // if (!fDeleteAll ...
// If we reach this point, we're going to delete this entry
// in the dictionary. So update cDel & cbDel.
DebugTrace(0, Dbg, ( "Deleting Entry (%s) @%lx: propid=%lx\n", fDeleteAll? "DeleteAll" : apid[iprop] == PropByteSwap(ENTRY_GetPropid(pent)) ? "replace by propid" : "replace by name", pent, PropByteSwap( ENTRY_GetPropid(pent) )));
cDel++; cbDel += _DictionaryEntryLength( pent );
// We don't need to continue through the caller's arrays,
// we can move on to the next dictionary entry.
break;
} // for (iprop = 0; iprop < cprop; iprop++)
} // for (i = 0, pent = &pdy->rgEntry[0]; ...
PROPASSERT(pent == Add2Ptr(pdy, cbDictOld)); PROPASSERT( _CodePage == CP_WINUNICODE ? IsDwordAligned( cbDel ) : TRUE );
cbDictOldD = DwordAlign(cbDictOld); cbChangeD = DwordAlign(cbDictOld + cbAdd - cbDel) - cbDictOldD;
cbstm = _oSection + _GetSectionHeader()->cbSection + _cbTail; oDictionary = _MapAddressToOffset(pdy); cbTail;
cbTail = cbstm - (_oSection + oDictionary + cbDictOldD);
// --------------------------------------------------------
// Before we change anything, grow the stream if necessary.
// --------------------------------------------------------
if (cbChangeD > 0) { DebugTrace(0, Dbg, ( "SetSize(%x) dictionary grow\n", cbstm + cbChangeD)); if (cbstm + cbChangeD > CBMAXPROPSETSTREAM) { StatusDiskFull(pstatus, "SetPropertyNames: 256k limit"); goto Exit; }
_MSTM(SetSize)(cbstm + cbChangeD, TRUE, (VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// reload all pointers into mapped image:
pdy = (DICTIONARY *) _MapOffsetToAddress(oDictionary);
// move everything after the dictionary back by cbChangeD bytes.
PropMoveMemory( "SetPropertyNames:TailBack", _GetSectionHeader(), Add2Ptr(pdy, cbDictOldD + cbChangeD), Add2Ptr(pdy, cbDictOldD), cbTail); }
// -------------------------------------------------------------------
// Walk through the existing dictionary and compact unmodified entries
// toward the front. New and modified entries will be appended later.
// -------------------------------------------------------------------
VOID *pvSrc; VOID *pvDst; ULONG cbCopy;
pvDst = pvSrc = pent = &pdy->rgEntry[0]; cbCopy = 0;
if (!fDeleteAll) { ULONG cb;
for (i = 0; i < PropByteSwap(pdy->cEntries); i++) { for (iprop = 0; iprop < cprop; iprop++) { if( apid[iprop] == PropByteSwap(ENTRY_GetPropid(pent)) ) { break; } if (fDeleteByName) // if deleting any properties by name
{ ULONG cbname;
_PropertyNameLength(appvNames[iprop], &cbname); if (cbname == CCh2CB( PropByteSwap( ENTRY_GetCch(pent) )) && _ComparePropertyNames( appvNames[iprop], ENTRY_GetSz(pent), FALSE, // appvNames & pent->sz may be dif endians
cbname) ) { break; // found an entry to be removed.
} } } // for (iprop = 0; iprop < cprop; iprop++)
cb = _DictionaryEntryLength( pent ); pent = _NextDictionaryEntry( pent );
if (iprop == cprop) // keep the dictionary entry
{ cbCopy += cb; } else // remove the dictionary entry
{ if (cbCopy != 0) { if (pvSrc != pvDst) { PropMoveMemory( "SetPropertyNames:Compact", _GetSectionHeader(), pvDst, pvSrc, cbCopy); } pvDst = Add2Ptr(pvDst, cbCopy); cbCopy = 0; } pvSrc = pent; } } // for (i = 0; i < PropByteSwap(pdy->cEntries); i++)
// Compact last chunk and point past compacted entries.
if (cbCopy != 0 && pvSrc != pvDst) { PropMoveMemory( "SetPropertyNames:CompactLast", _GetSectionHeader(), pvDst, pvSrc, cbCopy); } pent = (ENTRY UNALIGNED *) Add2Ptr(pvDst, cbCopy);
} // if (!fDeleteAll)
pdy->cEntries = PropByteSwap( PropByteSwap(pdy->cEntries) - cDel );
// ------------------------------------
// Append new and modified entries now.
// ------------------------------------
if (appvNames != NULL) { // Add each name to the property set.
for (iprop = 0; iprop < cprop; iprop++) { // See if this propid appears later in the array.
i = cprop; if (fDupPropid) { for (i = iprop + 1; i < cprop; i++) { if (apid[i] == apid[iprop]) { break; } } }
// See if this name appears later in the array.
if (i == cprop && fDupName) { ULONG cbname;
_PropertyNameLength(appvNames[iprop], &cbname);
for (i = iprop + 1; i < cprop; i++) { ULONG cbname2;
_PropertyNameLength(appvNames[i], &cbname2);
if (cbname == cbname2 && _ComparePropertyNames( appvNames[iprop], appvNames[i], TRUE, // Both names are the same endian
cbname)) { break; } } }
// If this propid appears only once or if it's the last instance,
// append the mapping entry.
if (i == cprop) { ULONG cbname;
// Set the PID & character-count fields for this entry.
_PropertyNameLength(appvNames[iprop], &cbname); ENTRY_SetPropid(pent, PropByteSwap( apid[iprop] ) ); ENTRY_SetCch(pent, PropByteSwap( CB2CCh( cbname ) ) );
// Copy the name into the dictionary.
RtlCopyMemory(ENTRY_GetSz(pent), appvNames[iprop], cbname);
// If this is a Unicode property set, we need to correct
// the byte-order.
if( CP_WINUNICODE == _CodePage ) { PBSBuffer( ENTRY_GetSz(pent), cbname, sizeof(WCHAR) ); }
// Zero-out the pad bytes.
RtlZeroMemory( Add2Ptr(pent->sz, cbname), DwordRemain((ULONG) pent->sz + cbname)); pent = _NextDictionaryEntry( pent ); } } // for (iprop = 0; iprop < cprop; iprop++)
// We've added all the names, now let's update the entry count.
pdy->cEntries = PropByteSwap( PropByteSwap(pdy->cEntries) + cAdd );
} // if (appvNames != NULL)
// Zero the possible partial DWORD at the end of the dictionary.
{ ULONG cb = (ULONG) ((BYTE *) pent - (BYTE *) pdy); PROPASSERT(DwordAlign(cb) == cbDictOldD + cbChangeD); RtlZeroMemory(pent, DwordRemain(cb)); }
// -----------------------------------------------------
// Adjust the remaining property offsets in the section.
// -----------------------------------------------------
PROPERTYIDOFFSET *ppo, *ppoMax; PROPERTYSECTIONHEADER *psh;
psh = _LoadPropertyOffsetPointers(&ppo, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; PROPASSERT(psh != NULL);
// Don't rely on the dictionary being the first property.
// Skip PID_DICTIONARY and adjust every other higher entry.
for ( ; ppo < ppoMax; ppo++) { if (ppo->dwOffset > oDictionary) { ppo->dwOffset += cbChangeD; PROPASSERT(ppo->propid != PID_DICTIONARY); } }
// Update the size of the section
psh->cbSection += cbChangeD;
if (cbChangeD < 0) { // move everything after the dictionary forward by cbChangeD bytes.
PropMoveMemory( "SetPropertyNames:TailUp", _GetSectionHeader(), Add2Ptr(pdy, cbDictOldD + cbChangeD), Add2Ptr(pdy, cbDictOldD), cbTail); } if (_cbTail != 0) { _PatchSectionOffsets(cbChangeD); }
// If we need to shrink the stream or if we are cleaning up after a
// previous shrink that failed, do it last.
cbNewSize = _MSTM(GetSize)(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if ( cbNewSize != cbstm + cbChangeD) { DebugTrace(0, Dbg, ( "SetSize(%x) dictionary shrink\n", cbstm + cbChangeD)); _MSTM(SetSize)(cbstm + cbChangeD, TRUE, (VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; }
// ----
// Exit
// ----
Exit:
// If we had to convert the array of names into a different
// codepage, delete those temporary buffers now.
if (appvNames != NULL && appvNames != (VOID **) aposz) { for (iprop = 0; iprop < cprop; iprop++) { _pma->Free( appvNames[iprop] ); } delete [] (char **) appvNames; }
DebugTrace(0, Dbg, ("SetPropertyNames() ==> s=%x\n", STATUS_SUCCESS)); return; }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_ValidateStructure
//
// Synopsis: validate property set structure
//
// Arguments: [pstatus] -- pointer to NTSTATUS code
//
// Returns: None
//+--------------------------------------------------------------------------
#if DBGPROP
VOID CPropertySetStream::_ValidateStructure(OUT NTSTATUS *pstatus) { PROPID propid; ULONG cb;
OLECHAR aocName[ CCH_MAXPROPNAMESZ]; ULONG cbName;
*pstatus = STATUS_SUCCESS;
// Walk through properties to make sure all properties are consistent
// and are contained within the section size. A NULL return value
// means _LoadProperty walked the entire section, so we can quit then.
for (propid = PID_CODEPAGE; propid != PID_ILLEGAL; propid++) { SERIALIZEDPROPERTYVALUE const *pprop;
pprop = GetValue(propid, &cb, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (NULL == pprop) { break; } }
// Walk through dictionary entries to make sure all entries are consistent
// and are contained within the dictionary size. A FALSE return value
// means QueryPropertyNameBuf walked the entire dictionary, so quit then.
for (propid = PID_CODEPAGE + 1; propid != PID_ILLEGAL; propid++) { BOOL fExists; cb = 0;
cbName = sizeof(aocName); fExists = QueryPropertyNameBuf(propid, aocName, &cbName, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if( !fExists ) { break; } }
if (_cSection > 1) { FORMATIDOFFSET const *pfo;
if (_cSection != 2) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateStructure: csection(%x) != 2", _cSection)); StatusCorruption(pstatus, "_ValidateStructure: csection != 2"); goto Exit; } pfo = _GetFormatidOffset(0); if (pfo->fmtid != guidDocumentSummary) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateStructure: DocumentSummary[0] fmtid")); StatusCorruption(pstatus, "_ValidateStructure: DocumentSummary[0] fmtid"); goto Exit; } if (!IsDwordAligned(pfo->dwOffset)) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateStructure: dwOffset[0] = %x", pfo->dwOffset)); StatusCorruption(pstatus, "_ValidateStructure: dwOffset[0]"); goto Exit; }
pfo = _GetFormatidOffset(1); if (pfo->fmtid != guidDocumentSummarySection2) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateStructure: DocumentSummary[1] fmtid")); StatusCorruption(pstatus, "_ValidateStructure: DocumentSummary[1] fmtid"); goto Exit; } if (!IsDwordAligned(pfo->dwOffset)) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateStructure: dwOffset[1] = %x", pfo->dwOffset)); StatusCorruption(pstatus, "_ValidateStructure: dwOffset[1]"); goto Exit; } } // if (_cSection > 1)
// ----
// Exit
// ----
Exit:
return; } #endif
//+--------------------------------------------------------------------------
// Member: fnPropidCompare
//
// Synopsis: qsort helper to compare propids in a PROPERTYIDOFFSET array.
//
// Arguments: [ppo1] -- pointer to PROPERTYIDOFFSET 1
// [ppo2] -- pointer to PROPERTYIDOFFSET 2
//
// Returns: difference
//+--------------------------------------------------------------------------
#if DBGPROP
INT __cdecl fnPropidCompare(VOID const *ppo1, VOID const *ppo2) { return(((PROPERTYIDOFFSET const *) ppo1)->propid - ((PROPERTYIDOFFSET const *) ppo2)->propid); } #endif
//+--------------------------------------------------------------------------
// Member: fnOffsetCompare
//
// Synopsis: qsort helper to compare offsets in a PROPERTYIDOFFSET array.
//
// Arguments: [ppo1] -- pointer to PROPERTYIDOFFSET 1
// [ppo2] -- pointer to PROPERTYIDOFFSET 2
//
// Returns: difference
//+--------------------------------------------------------------------------
INT __cdecl fnOffsetCompare(VOID const *ppo1, VOID const *ppo2) { return(((PROPERTYIDOFFSET const *) ppo1)->dwOffset - ((PROPERTYIDOFFSET const *) ppo2)->dwOffset); }
//+--------------------------------------------------------------------------
// Member: GetStringLength
//
// Synopsis: return length of possibly unicode string.
//
// Arguments: [CodePage] -- TRUE if string is Unicode
// [pwsz] -- pointer to string
// [cb] -- MAXULONG or string length with L'\0' or '\0'
//
// Returns: length of string in bytes including trailing L'\0' or '\0'
//+--------------------------------------------------------------------------
ULONG GetStringLength( IN USHORT CodePage, IN WCHAR const *pwsz, IN ULONG cb) { ULONG i;
if (CodePage == CP_WINUNICODE) { for (i = 0; i < cb/sizeof(WCHAR); i++) { if (pwsz[i] == L'\0') { break; } } PROPASSERT(cb == MAXULONG || cb == (i + 1) * sizeof(WCHAR)); return((i + 1) * sizeof(WCHAR)); } else { char *psz = (char *) pwsz;
for (i = 0; i < cb; i++) { if (psz[i] == '\0') { break; } } PROPASSERT(cb == MAXULONG || cb == (i + 1) * sizeof(char)); return((i + 1) * sizeof(char)); } }
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_ValidateProperties
//
// Synopsis: validate properties
//
// Arguments: [pstatus] -- pointer to NTSTATUS code
//
// Returns: None
//+--------------------------------------------------------------------------
#if DBGPROP
VOID CPropertySetStream::_ValidateProperties(OUT NTSTATUS *pstatus) const { PROPERTYIDOFFSET *apo = NULL; PROPERTYSECTIONHEADER const *psh = _GetSectionHeader(); static ULONG cValidate = 0; ULONG cbwasted = 0; ULONG cbtotal = 0;
*pstatus = STATUS_SUCCESS;
cValidate++; DebugTrace(0, DEBTRACE_PROPVALIDATE, ( "_ValidateProperties(%x ppsstm=%x state=%x pph=%x)\n", cValidate, this, _State, _pph));
if (psh->cProperties != 0) { PROPERTYIDOFFSET *ppo, *ppoMax;
apo = newk(mtPropSetStream, NULL) PROPERTYIDOFFSET[psh->cProperties + 1]; if (apo == NULL) { *pstatus = STATUS_NO_MEMORY; goto Exit; }
RtlCopyMemory( apo, psh->rgprop, psh->cProperties * CB_PROPERTYIDOFFSET);
ppoMax = apo + psh->cProperties; ppoMax->propid = PID_ILLEGAL; ppoMax->dwOffset = psh->cbSection;
// Sort by property id and check for duplicate propids:
qsort(apo, psh->cProperties, sizeof(apo[0]), fnPropidCompare);
for (ppo = apo; ppo < ppoMax; ppo++) { if (ppo->propid == PID_ILLEGAL || ppo->propid == ppo[1].propid) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateProperties(bad propid=%x @%x)\n", ppo->propid, ppo->dwOffset)); StatusCorruption(pstatus, "_ValidateProperties: bad or dup propid"); goto Exit; } }
// Sort by offset and check for overlapping values:
qsort(apo, psh->cProperties, sizeof(apo[0]), fnOffsetCompare);
cbtotal = _oSection; for (ppo = apo; ppo < ppoMax; ppo++) { ULONG cbdiff, cbprop, cbpropraw; SERIALIZEDPROPERTYVALUE const *pprop;
cbprop = MAXULONG; cbpropraw = cbprop; cbdiff = ppo[1].dwOffset - ppo->dwOffset; if (IsDwordAligned(ppo->dwOffset) && IsDwordAligned(ppo[1].dwOffset)) { pprop = (SERIALIZEDPROPERTYVALUE const *) _MapOffsetToAddress(ppo->dwOffset);
if (ppo->propid == PID_DICTIONARY) { cbprop = _DictionaryLength( (DICTIONARY const *) pprop, cbdiff, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
cbpropraw = cbprop; cbprop = DwordAlign(cbprop); } else { cbprop = PropertyLength(pprop, cbdiff, 0, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; cbpropraw = cbprop; } DebugTrace(0, DEBTRACE_PROPVALIDATE, ( "_ValidateProperties(%x) i=%x cb=%x/%x/%x @%x/%x pid=%x\n", cValidate, ppo - apo, cbprop, cbdiff, ppo->dwOffset, pprop, ppo->propid)); cbtotal += cbdiff;
// Technically, the OLE spec allows extra unused space
// between properties, but this implementation never
// writes out streams with space between properties.
if (cbdiff == cbprop) { continue; } } DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateProperties(bad value length: propid=%x @%x/%x cb=%x/%x/%x ppsstm=%x)\n", ppo->propid, ppo->dwOffset, pprop, cbpropraw, cbprop, cbdiff, this)); StatusCorruption(pstatus, "_ValidateProperties: bad property length"); goto Exit;
} // for (ppo = apo; ppo < ppoMax; ppo++)
} // if (psh->cProperties != 0)
// ----
// Exit
// ----
Exit:
delete [] apo;
DebugTrace(0, cbwasted != 0? 0 : Dbg, ( "_ValidateProperties(wasted %x bytes, total=%x)\n", cbwasted, cbtotal));
} #endif
#if DBGPROP
typedef struct tagENTRYVALIDATE // ev
{ ENTRY UNALIGNED const *pent; CPropertySetStream const *ppsstm; } ENTRYVALIDATE; #endif
//+--------------------------------------------------------------------------
// Member: fnEntryPropidCompare
//
// Synopsis: qsort helper to compare propids in a ENTRYVALIDATE array.
//
// Arguments: [pev1] -- pointer to ENTRYVALIDATE 1
// [pev2] -- pointer to ENTRYVALIDATE 2
//
// Returns: difference
//+--------------------------------------------------------------------------
#if DBGPROP
INT __cdecl fnEntryPropidCompare(VOID const *pev1, VOID const *pev2) { return( ENTRY_GetPropid( ((ENTRYVALIDATE const *) pev1)->pent) - ENTRY_GetPropid( ((ENTRYVALIDATE const *) pev2)->pent) ); } #endif
//+--------------------------------------------------------------------------
// Member: fnEntryNameCompare
//
// Synopsis: qsort helper to compare names in a ENTRYVALIDATE array.
//
// Arguments: [pev1] -- pointer to ENTRYVALIDATE 1
// [pev2] -- pointer to ENTRYVALIDATE 2
//
// Returns: difference
//+--------------------------------------------------------------------------
#if DBGPROP
INT __cdecl fnEntryNameCompare(VOID const *pev1, VOID const *pev2) { ENTRY UNALIGNED const *pent1; ENTRY UNALIGNED const *pent2; INT rc;
pent1 = ((ENTRYVALIDATE const *) pev1)->pent; pent2 = ((ENTRYVALIDATE const *) pev2)->pent;
rc = PropByteSwap(ENTRY_GetCch(pent1)) - PropByteSwap(ENTRY_GetCch(pent2)); if (rc == 0) { rc = !((ENTRYVALIDATE const *) pev1)->ppsstm->_ComparePropertyNames( ENTRY_GetSz(pent1), ENTRY_GetSz(pent2), TRUE, // Both names have the same byte-order
( (ENTRYVALIDATE const *) pev1 )->ppsstm->CCh2CB(PropByteSwap( ENTRY_GetCch(pent1)))); } return(rc); } #endif
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::_ValidateDictionary
//
// Synopsis: validate property set dictionary
//
// Arguments: [pstatus] -- pointer to NTSTATUS code
//
// Returns: None
//+--------------------------------------------------------------------------
#if DBGPROP
VOID CPropertySetStream::_ValidateDictionary(OUT NTSTATUS *pstatus) { DICTIONARY const *pdy; ULONG cbDict; // BYTE granular size!
ENTRYVALIDATE *aev = NULL; ENTRYVALIDATE *pev, *pevMax; PROPERTYSECTIONHEADER const *psh; ENTRY UNALIGNED const *pent; ENTRY entMax; VOID const *pvDictEnd;
*pstatus = STATUS_SUCCESS;
pdy = (DICTIONARY const *) _LoadProperty(PID_DICTIONARY, &cbDict, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
if (pdy != NULL && PropByteSwap(pdy->cEntries) != 0) { aev = newk (mtPropSetStream, NULL) ENTRYVALIDATE[ PropByteSwap(pdy->cEntries) + 1 ]; if (aev == NULL) { *pstatus = STATUS_NO_MEMORY; goto Exit; }
psh = _GetSectionHeader(); pent = pdy->rgEntry; pvDictEnd = Add2ConstPtr(pdy, cbDict); pevMax = aev + PropByteSwap( pdy->cEntries );
for (pev = aev; pev < pevMax; pev++) { ULONG cb = _DictionaryEntryLength( pent );
if (Add2ConstPtr(pent, cb) > pvDictEnd) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateDictionary(bad entry size for propid=%x)\n", PropByteSwap( ENTRY_GetPropid(pev->pent) ))); StatusCorruption(pstatus, "ValidateDictionary: entry size"); goto Exit; } pev->pent = pent; pev->ppsstm = this;
#if DBGPROP
#if LITTLEENDIAN
if (_CodePage == CP_WINUNICODE) { PROPASSERT(IsUnicodeString((WCHAR const *) ENTRY_GetSz(pent), CCh2CB(PropByteSwap(ENTRY_GetCch(pent) )))); } else { PROPASSERT(IsAnsiString((char const *) ENTRY_GetSz(pent), CCh2CB( PropByteSwap(ENTRY_GetCch(pent) )))); } #endif // LITTLEENDIAN
#endif // DBGPROP
pent = _NextDictionaryEntry( pent ); } if ((VOID const *) pent != pvDictEnd) { StatusCorruption(pstatus, "ValidateDictionary: end offset"); goto Exit; } entMax.cch = 0; entMax.propid = PID_ILLEGAL; pevMax->pent = &entMax; pevMax->ppsstm = this;
// Sort by property id and check for duplicate propids:
qsort(aev, PropByteSwap(pdy->cEntries), sizeof(aev[0]), fnEntryPropidCompare);
for (pev = aev; pev < pevMax; pev++) { if (PID_ILLEGAL == PropByteSwap( ENTRY_GetPropid(pev->pent)) || PropByteSwap( ENTRY_GetPropid(pev[1].pent) ) == PropByteSwap( ENTRY_GetPropid(pev->pent) )) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateDictionary(bad propid=%x)\n", PropByteSwap( ENTRY_GetPropid(pev->pent) ))); StatusCorruption(pstatus, "_ValidateDictionary: bad or dup propid"); goto Exit; } }
// Sort by property name and check for duplicate names:
qsort(aev, PropByteSwap(pdy->cEntries), sizeof(aev[0]), fnEntryNameCompare);
for (pev = aev; pev < pevMax; pev++) { if ( ENTRY_GetCch(pev->pent) == 0 || ( ENTRY_GetCch(pev->pent) == ENTRY_GetCch(pev[1].pent) && _ComparePropertyNames( ENTRY_GetSz(pev->pent), ENTRY_GetSz(pev[1].pent), TRUE, // Names are the same byte-order
CCh2CB(PropByteSwap(ENTRY_GetCch(pev->pent)))) ) ) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateDictionary(bad name for propid=%x)\n", PropByteSwap( ENTRY_GetPropid(pev->pent) ))); StatusCorruption(pstatus, "_ValidateDictionary: bad or dup name"); goto Exit; } } // for (pev = aev; pev < pevMax; pev++)
} // if (pdy != NULL && pdy->cEntries != 0)
// ----
// Exit
// ----
Exit:
delete [] aev;
} #endif // DBGPROP
//+--------------------------------------------------------------------------
// Member: CPropertySetStream::Validate
//
// Synopsis: validate entire property stream
//
// Arguments: [pstatus] -- pointer to NTSTATUS code
//
// Returns: None
//+--------------------------------------------------------------------------
#if DBGPROP
BOOLEAN fValidatePropSets = KERNELSELECT(DBG, TRUE);
VOID CPropertySetStream::Validate(OUT NTSTATUS *pstatus) { if (fValidatePropSets && (_State & CPSS_USERDEFINEDDELETED) == 0) { ULONG cbstm = _MSTM(GetSize)(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit;
// Walk through section headers to make sure all sections are contained
// within the stream size.
if (_ComputeMinimumSize(cbstm, pstatus) != 0) { // If an error had occurred in the above call,
// it would have returned zero.
_ValidateStructure( pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit;
_ValidateProperties( pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit;
_ValidateDictionary( pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit;
_ComputeMinimumSize(cbstm, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; } } // if (fValidatePropSets && (_State & CPSS_USERDEFINEDDELETED) == 0)
// ----
// Exit
// ----
Exit:
return; } #endif
|