//+-------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1993 // // File: propstm.cxx // // Contents: property set value extraction code // // History: 15-Jul-94 brianb created // 12-Aug-94 SethuR Included Assertions for # of sections // split PropertySet class into // CPropertySetStream & CPropertySetStorage // Included Update methods on the property // stream. // 22-Feb-96 MikeHill DWORD-align the dictionary entries, // & use char-counts for dict entries. // 29-Feb-96 MikeHill Moved _DictionaryEntryLength and _NextDictionaryEntry // inlines here from propstm.hxx. // 09-May-96 MikeHill - Keep the dictionary in the UserDef propset // immediately after the last entry in the PID/Offset // array (for Office95 compatibility). // - Create an empty dictionary in the UD propset // when it is created. If we wait till later, // we can't make the dictionary the first property, // which is required by Office95. // - Provide compatibility with Publisher95 (which doesn't // DWORD-align the section/stream size). // - Provide compatibility with PowerPoint 4.0 (which // over-pads some properties, and under-pads others). // - Don't try to unpack the DocParts and HeadingPair // DocSumInfo properties in Ansi property sets. // 22-May-96 MikeHill - Return the OSVersion on an Open. // - Use the PropSet's code page, not the system's. // 11-Jun-96 MikeHill - Initialize all members in the constructor. // 25-Jul-96 MikeHill - Removed usage of Win32 SEH. // - BSTRs & prop names: WCHAR => OLECHAR. // - Added big-endian support. // - Determine the OSVer at run-time. // - Fix for Excel 5.0a compatibility. // 26-Nov-96 MikeHill Handle invalid oSection values. // 10-Mar-98 MikeHill - Change "Pr" functions to "Stg". // - Added asserts for new VTs. // 06-May-98 MikeHill - Use CoTaskMem rather than new(k)/delete // - Removed usage of UnicodeCallouts. // 18-May-98 MikeHIll - Fixed typos. // 11-June-98 MikeHill - Dbg output. // - Allow codepage & lcid to be changed // if property set is empty. // - Validate pid_behavior in property set. // - Silently ignore PID_ILLEGAL in SetPropertyNames. // 18-Aug-98 MikeHill - Make reserved range 0x80000000 to 0x8c000000 // read-only unless the property is understood. // // Notes: // // 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. Since the property set streams need to be parsed // in the kernel mode (OFS driver) as well as the user mode, this class // encapsulates a stream implementation (IMappedStream). This is different // from other stream implementations in that the fundamental mechanism provided // for acessing the contents is Map/Unmap rather than Read/Write. There are // two user mode implementations of this IMappedStream interface, one for // docfile streams, and another for native streams. There is one // implementation in kernel mode for the OFS driver. For more details, // refer to propstm.hxx. //--------------------------------------------------------------------------- #include #include #if DBGPROP #include // for sprintf/strcpy #endif #include "propvar.h" #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) (( (p) & (sizeof(ULONG) - 1)) == 0) #endif #ifndef DwordRemain #define DwordRemain(cb) \ ((sizeof(ULONG) - ((cb) % sizeof(ULONG))) % sizeof(ULONG)) #endif // Information for the the OS Version field of the // property set header. #if defined(WINNT) && !defined(IPROPERTY_DLL) # define PROPSETVER_CURRENT MAKEPSVER(OSKIND_WIN32, WINVER >> 8, WINVER & 0xff) #endif #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) #define CP_CREATEDEFAULT(state) GetACP() #define LCID_CREATEDEFAULT GetUserDefaultLCID() #if DBGPROP #define StatusCorruption(pstatus, szReason) \ _StatusCorruption(szReason " ", pstatus) #else #define StatusCorruption(pstatus, szReason) \ _StatusCorruption(pstatus) #endif VOID PrpConvertToUnicode( IN CHAR const *pch, IN ULONG cb, IN USHORT CodePage, OUT WCHAR **ppwc, OUT ULONG *pcb, OUT NTSTATUS *pstatus); VOID PrpConvertToMultiByte( IN WCHAR const *pwc, IN ULONG cb, IN USHORT CodePage, OUT CHAR **ppch, OUT ULONG *pcb, OUT NTSTATUS *pstatus); // // Re-direct PrEqual[Unicode]String routines // // These macros redirect two NTDLL routines which don't exist in // the IProperty DLL. They are redirected to CRT calls. // // Note: These redirections assume that the Length and // MaximumLength fields, on both String structures, are the // same (e.g. s1.len == s1.maxlen == s2.len == s2.maxlen). // #ifdef IPROPERTY_DLL #error Need to replace the icmp routines with CharUpper-based code for correct locale ID #define RtlEqualString(String1,String2,fCaseInSensitive) \ fCaseInSensitive \ ? ( !_strnicmp( (String1)->Buffer, \ (String2)->Buffer, \ (String1)->MaximumLength) ) \ : ( !strncmp( (String1)->Buffer, \ (String2)->Buffer, \ (String1)->MaximumLength) ) #define RtlEqualUnicodeString(String1,String2,fCaseInSensitive) \ fCaseInSensitive \ ? ( !_wcsnicmp( (String1)->Buffer, \ (String2)->Buffer, \ (String1)->MaximumLength / sizeof(WCHAR) )) \ : ( !wcsncmp( (String1)->Buffer, \ (String2)->Buffer, \ (String1)->MaximumLength / sizeof(WCHAR) )) #endif // #ifdef IPROPERTY_DLL #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 rgBuf[], ULONG cbBuf) { //char *p = buf; ULONG iBuf = 0; 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 (0 != iBuf) { rgBuf[ iBuf++ ] = ' '; } PropSprintfA( &rgBuf[iBuf], cbBuf-iBuf, "%02.2x", *pb++ ); iBuf = (ULONG)strlen(rgBuf); } } rgBuf[iBuf] = '\0'; PROPASSERT(iBuf + sizeof(szDots) <= CB_VALUESTRING); if (fOverflow) { StringCbCopyA( &rgBuf[iBuf], cbBuf-iBuf, szDots ); } return( &rgBuf[iBuf] ); } #define CB_VARIANT_TO_STRING 35 char * VariantToString(PROPVARIANT const &var, ULONG cbprop, char rgBuf[], ULONG cbBuf ) { ULONG iBuf = 0; PROPASSERT( cbprop >= CB_VARIANT_TO_STRING ); // Add the VT to the output buffer. PropSprintfA( &rgBuf[iBuf], cbBuf-iBuf, "vt=%04.4x", var.vt ); iBuf = (ULONG)strlen(rgBuf); PropSprintfA( &rgBuf[iBuf], cbBuf-iBuf, ", val=(%08.8x, %08.8x)", var.uhVal.LowPart, var.uhVal.HighPart ); iBuf = (ULONG)strlen(rgBuf); rgBuf[iBuf] = '\0'; PROPASSERT( iBuf == CB_VARIANT_TO_STRING); return( &rgBuf[iBuf] ); } #endif // #if DBGPROP //+-------------------------------------------------------------------------- // Member: CPropertySetStream::_DictionaryEntryLength // // Synopsis: Calculate the length of an entry in the // dictionary. This is non-trivial because // it is codepage-dependent. // // Note: We must not attempt to access the name itself, // as this routine can be called before those bytes // have been validated. // // Arguments: [pent] -- pointer to a dictionary entry. // // Returns: The entry's length. //+-------------------------------------------------------------------------- inline ULONG CPropertySetStream::_DictionaryEntryLength( IN ENTRY UNALIGNED const * pent ) const { ULONG ulSize ; // If this is a Unicode property set, it should be DWORD-aligned. PROPASSERT( _CodePage != CP_WINUNICODE || IsDwordAligned( (ULONG_PTR)pent )); // The size consists of the length of the // PROPID and character count ... ulSize = CB_DICTIONARY_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::_StatusCorruption // // 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 propDbg(( DEB_ERROR, "_StatusCorruption(%s, psstm=%lx, mapstm=%lx, flags=%x)\n", szReason, this, _pmstm, _Flags)); #endif *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 UNALIGNED *pvSrc, ULONG cbMove) { propDbg(( DEB_ITRACE, "%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 IsLocalizationPropid(PROPID pid) { return( PID_CODEPAGE == pid || PID_LOCALE == pid ); } inline BOOLEAN IsReadOnlyPropid(PROPID pid) { return( pid == PID_DICTIONARY || pid == PID_MODIFY_TIME || pid == PID_SECURITY || pid == PID_BEHAVIOR || IsLocalizationPropid(pid) || ( PID_MIN_READONLY <= pid && PID_MAX_READONLY >= pid ) ); } //+-------------------------------------------------------------------------- // 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) { CoTaskMemFree( _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 = (CStreamChunk*) CoTaskMemAlloc( sizeof(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 (zero-relative) // [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(iSection < _pph->reserved ); // Get the section offset, after verifying that we can read all // of the FmtID/Offset table. if (cbstm >= CB_PROPERTYSETHEADER + (iSection + 1) * CB_FORMATIDOFFSET) oSection = _GetFormatidOffset(iSection)->dwOffset; else StatusCorruption (pstatus, "GetSectionHeader(i): stream size too short to read section offset"); // Create a pointer to the section header, after verifying that we can // read all of the section header. We don't verify that we can actually // read the whole section (using cbSection), the caller must be responsible // for this. // We have to check oSection first, then oSection+cb_psh, because oSection // could be a negative number (such as 0xffffffff), so adding it to cb_psh // could make it look valid. if (cbstm >= oSection && cbstm >= oSection + CB_PROPERTYSECTIONHEADER) { psh = (PROPERTYSECTIONHEADER *) Add2Ptr(_pph, oSection); } else StatusCorruption (pstatus, "GetSectionHeader(i): stream size too short to read section header"); // Finally, ensure that the section is 32 bit aligned. We handle several // compatibility problems in the _Fix* routines, but not a misaligned // section header. if( !IsDwordAligned( (ULONG_PTR) psh )) StatusCorruption( pstatus, "GetSectionHeader(i): section header is misaligned" ); // ---- // Exit // ---- Exit: return(psh); } //+-------------------------------------------------------------------------- // Member: CPropertySetStream::_SearchForCodePage, private // // Synopsis: Searches a section of a property set for the code page, // LCID, and Behavior properties. // // This routine searches for these special properties by iterating // through the PID/Offset array in search of // PID_CODEPAGE, PID_BEHAVIOR, and PID_LOCALE. 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/PID_BEHAVIOR exist, they are put // into _CodePage/_grfBehavior. // If either doesn't exist, the corresponding member // variable is left unchanged. // // Arguments: [pstatus] -- Pointer to NTSTATUS code. // // Notes: We do *not* assume that the property set's // cbSection field is valid (this was added to handle a // special-case compatibility problem). // // Returns: None. //+-------------------------------------------------------------------------- VOID CPropertySetStream::_SearchForCodePage( OUT NTSTATUS *pstatus ) { PROPERTYSECTIONHEADER UNALIGNED *psh; PROPERTYIDOFFSET UNALIGNED *ppo; PROPERTYIDOFFSET UNALIGNED *ppoMax; BOOL fCodePageFound = FALSE; BOOL fBehaviorFound = FALSE; BOOL fLocaleFound = FALSE; ULONG cbstm; IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::_SearchForCodePage" ); *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. psh = _GetSectionHeader(); // Ensure that we can at least read the section header and // PID/Offset table. cbstm = _MSTM(GetSize)(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; if (cbstm < _oSection || cbstm < _oSection + CB_PROPERTYSECTIONHEADER || cbstm < _oSection + CB_PROPERTYSECTIONHEADER + psh->cProperties * CB_PROPERTYIDOFFSET ) { StatusCorruption(pstatus, "_SearchForCodePage: stream too short to read section header"); goto Exit; } // Calculate the first & last PID/Offset pointers. // We can't use _LoadPropertyOffsetPointers, because it assumes // alignment. ppo = psh->rgprop; ppoMax = psh->rgprop + psh->cProperties; // Search the PID/Offset array for PID_CODEPAGE & PID_BEHAVIOR for ( ; ppo < ppoMax; ppo++) { if( PID_CODEPAGE == ppo->propid || PID_BEHAVIOR == ppo->propid || PID_LOCALE == ppo->propid ) { SERIALIZEDPROPERTYVALUE UNALIGNED *pprop; // Get the real address of serialized property. pprop = (SERIALIZEDPROPERTYVALUE *) _MapOffsetToAddress( ppo->dwOffset ); // Check for corruption. Both properties are <= DWORD in size if ( cbstm < (_oSection + ppo->dwOffset + CB_SERIALIZEDPROPERTYVALUE + sizeof(DWORD)) ) { StatusCorruption(pstatus, "_SearchForCodePage"); goto Exit; } if( PID_CODEPAGE == ppo->propid ) { if( VT_I2 != PropByteSwap(pprop->dwType) ) { StatusCorruption(pstatus, "_SearchForCodePage"); goto Exit; } fCodePageFound = TRUE; _CodePage = PropByteSwap( *reinterpret_cast(&pprop->rgb) ); } else if( PID_BEHAVIOR == ppo->propid ) { if( VT_UI4 != PropByteSwap(pprop->dwType) ) { StatusCorruption(pstatus, "_SearchForCodePage"); goto Exit; } fBehaviorFound = TRUE; _grfBehavior = PropByteSwap( *reinterpret_cast(&pprop->rgb) ); } else if( PID_LOCALE == ppo->propid ) { if( VT_UI4 != PropByteSwap(pprop->dwType) ) { StatusCorruption( pstatus, "_SearchForCodePage"); goto Exit; } fLocaleFound = TRUE; _Locale = PropByteSwap( *reinterpret_cast(&pprop->rgb) ); } if( fCodePageFound && fBehaviorFound && fLocaleFound ) break; } // if( PID_CODEPAGE == ppo->propid || PID_BEHAVIOR == ppo->propid ) } // for ( ; ppo < ppoMax; ppo++) // ---- // Exit // ---- Exit: propDbg(( DEB_ITRACE, "CodePage=0x%x, Behavior=0x%x", _CodePage, _grfBehavior )); return; } // CPropertySetStream::_SearchForCodePage() //+-------------------------------------------------------------------------- // 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::_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 (ULONG)((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 (ULONG) ((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 IMappedStream *pmstm, // mapped stream impelementation IN PMemoryAllocator *pma // caller's memory allocator ) : _Flags((BYTE) Flags), _State(0), _pmstm(pmstm), _pma(pma), _pph(NULL) { // GetACP returns a UINT, but the property set only holds a USHORT. PROPASSERT( USHRT_MAX >= CP_CREATEDEFAULT(_State) ); _CodePage = (USHORT)CP_CREATEDEFAULT(_State); // Default if not present _Locale = LCID_CREATEDEFAULT; // Default if not present PROPASSERT(_Flags == Flags); // Should fit in a byte _oSection = 0; _cSection = 0; _cbTail = 0; _grfBehavior = 0; } //+-------------------------------------------------------------------------- // Member: CPropertySetStream::Close // // Synopsis: shutdown property set prior to calling destructor // // Arguments: [pstatus] -- Pointer to NTSTATUS code. // // Returns: None // // Notes: This method does *not* require that _pph be valid, // since it makes no use of it. If it were required, then // we would have to call ReOpen, which would be a waste // since we're closing down anyway. // //--------------------------------------------------------------------------- VOID CPropertySetStream::Close(OUT NTSTATUS *pstatus) { IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::Close" ); *pstatus = STATUS_SUCCESS; PROPASSERT( (_Flags & CREATEPROP_MODEMASK) != CREATEPROP_READ || !IsModified()); _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) IN DWORD grfBehavior, // PROPSET_BEHAVIOR_* OUT NTSTATUS *pstatus ) { *pstatus = STATUS_SUCCESS; LOADSTATE LoadState; PROPASSERT(!_IsMapped()); IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::Open" ); propTraceParameters(( "pfmtid=%p, pclsid=%p, LocaleId=%d, pOSVersion=%p, CodePage=%d, grfBehavior=0x%x", pfmtid, pclsid, LocaleId, pOSVersion, CodePage, grfBehavior )); 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 // PrOnMappedStreamEvent. _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); #if DBG if( LOADSTATE_BADFMTID == LoadState ) DebugTrace(0, DEBTRACE_WARN, ("_LoadHeader: LoadState=%x\n", LoadState)); #endif *pstatus = STATUS_PROPSET_NOT_FOUND; goto Exit; } _Create( pfmtid, pclsid, LocaleId, CodePage, LoadState, grfBehavior, 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)) { propDbg(( DEB_ERROR, "Open(%s) downlevel: %x", (_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: if( STATUS_PROPSET_NOT_FOUND == *pstatus ) propSuppressExitErrors(); 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; IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::ReOpen" ); *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) { propDbg(( DEB_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 // [fCreateDictionary] // -- TRUE => create empty dictionary // // Returns: None //--------------------------------------------------------------------------- // Serialized Code-Page size #define CB_CODEPAGE (CB_SERIALIZEDPROPERTYVALUE + DwordAlign(sizeof(USHORT))) // Serialized Locale ID (LCID) size. #define CB_LOCALE (CB_SERIALIZEDPROPERTYVALUE + sizeof(ULONG)) // PID_BEHAVIOR property (VT_UI4) #define CB_BEHAVIOR (CB_SERIALIZEDPROPERTYVALUE + 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 proset). // (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; IFDBG( HRESULT hr = S_OK ); propITrace( "CPropertySetStream::_InitSection" ); 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 and/or // a behavior property. dwPropValOffset identifies the location of the // next property value to be written. psh->cProperties = 2; // Always write codepage & local // dwPropValOffset = CB_PROPERTYSECTIONHEADER + 2 * CB_PROPERTYIDOFFSET; // Don't add in CB_PROPERTYIDOFFSET yet psh->cbSection = CB_PROPERTYSECTIONHEADER + CB_CODEPAGE + CB_LOCALE; // Finish calculating cProperties if( fCreateDictionary ) { psh->cProperties++; // Write an empty dictionary too psh->cbSection += CB_EMPTYDICTSIZE; } if( 0 != _grfBehavior ) { psh->cProperties++; // Write a behavior property too psh->cbSection += CB_BEHAVIOR; } // Based on cProperties, finish calculating cbSection psh->cbSection += psh->cProperties * CB_PROPERTYIDOFFSET; 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 to be first // and it doesn't do any harm to put it there. dwPropValOffset = CB_PROPERTYSECTIONHEADER + psh->cProperties * CB_PROPERTYIDOFFSET; 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 ) // Also if requested by the caller, create a behavior property. if( 0 != _grfBehavior ) { // Fill in the PID/Offset table psh->rgprop[ ulPropIndex ].propid = PID_BEHAVIOR; psh->rgprop[ ulPropIndex ].dwOffset = dwPropValOffset; // Fill in the property value pprop = (SERIALIZEDPROPERTYVALUE *) Add2Ptr( psh, dwPropValOffset ); pprop->dwType = PropByteSwap( (DWORD) VT_UI4 ); *reinterpret_cast(pprop->rgb) = PropByteSwap( _grfBehavior ); // Advance the table & value indices ulPropIndex++; dwPropValOffset += CB_BEHAVIOR; } // if( 0 != _grfBehavior ) // 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 ); // ulPropIndex++; // dwPropValOffset += CB_LOCALE; } // CPropertySetStream::_InitSection //+--------------------------------------------------------------------------- // Member: _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 // -------------- // IFDBG( HRESULT &hr = *pstatus ); // propITrace( "CPropertySetStream::_MultiByteToWideChar" ); *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); // propTraceParameters(( "pch=%s, cb=%d, CodePage=%d, ppwc=%p, pcb=%p", // pch, cb, CodePage, ppwc, pcb )); // ------------------ // 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 = MultiByteToWideChar( CodePage, // Source codepage 0, // Flags pch, // Source string cb, // Source string length *ppwc, // Target string *pcb / sizeof(WCHAR) ); // 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: _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 // -------------- IFDBG( HRESULT &hr = *pstatus ); // propITrace( "CPropertySetStream::_WiteCharToMultiByte" ); *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); // propTraceParameters(( "pwc=%ws, cch=%d, CodePage=%d, ppch=%p, pcb=%p", // pwc, cch, CodePage, ppch, pcb )); // ------------------ // 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 = WideCharToMultiByte( 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) { propDbg(( DEB_ITRACE, "_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 property set headers are valid, the // propset and section headers are byte-swapped. // Note that if the property set is invalid, this // routine may only partially swap it. Therefore, // the caller must ensure in this case that no // attempt is made to use the property set. // // 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 // ---------- IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::ByteSwapHeaders" ); propTraceParameters( "pph=%p, cbstm=%d", pph, cbstm ); *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; } // Get a local copy of this FMTID/Offset array entry // If it is propset-endian format, swap to make usable. pfoReal[ ulSectionIndex ].fmtid = pfoPropSet[ulSectionIndex].fmtid; pfoReal[ ulSectionIndex ].dwOffset = pfoPropSet[ulSectionIndex].dwOffset; if( fToSystemEndian ) { PropByteSwap( &pfoReal[ulSectionIndex].fmtid ); PropByteSwap( &pfoReal[ulSectionIndex].dwOffset ); } // Swap this FMTID/Offset entry in place. PropByteSwap( &pfoPropSet[ulSectionIndex].fmtid ); PropByteSwap( &pfoPropSet[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; } // Get local copies of the section & property counts. // Again we may need to swap them from propset-endian format // in order to make them usable. cbSection = psh->cbSection; cProperties = psh->cProperties; if( fToSystemEndian) { PropByteSwap( &cbSection ); PropByteSwap( &cProperties ); } // Swap the two fields at the top of the section header. PropByteSwap( &psh->cbSection ); PropByteSwap( &psh->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) { BOOLEAN fSuccess = FALSE; FORMATIDOFFSET *pfo; ULONG cbstmNew; PROPERTYSECTIONHEADER *psh; *pstatus = STATUS_SUCCESS; IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::_CreateUserDefinedSection" ); propTraceParameters(( "LoadState=%d, LocaleId=0x%x", LoadState, LocaleId )); 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 (why are we doing this if cbmove is zero)? 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, IN DWORD grfBehavior, OUT NTSTATUS *pstatus ) { ULONG cb; FORMATIDOFFSET *pfo; ULONG cSectionT; *pstatus = STATUS_SUCCESS; IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::_Create" ); propTraceParameters(( "pfmtid=%p, pclsid=%p, LocaleId=%d, CodePage=%d, LoadState=%d, grfBehavior=0x%x", pfmtid, pclsid, LocaleId, CodePage, LoadState, grfBehavior )); _SetModified(pstatus); if( !NT_SUCCESS(*pstatus) ) { TraceStatus( "_Create: Couldn't SetModified" ); goto Exit; } // Set the size of the stream to correspond to the header for the // property set as well as the section. _CodePage = CodePage; _grfBehavior = grfBehavior; 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. PROPASSERT( NT_SUCCESS(*pstatus) ); 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 ); if( 0 != _grfBehavior ) cb += CB_PROPERTYIDOFFSET + CB_BEHAVIOR; propDbg(( DEB_ITRACE, "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 OS Version in the header. // Getting the current OS version depends on the OS. #if defined(_MAC) { // Get the Mac System Version (e.g., 7.53). If we get an API error, // we won't treat it as fatal, we'll just set the version to 0. OSErr oserr; SysEnvRec theWorld; oserr = SysEnvirons( curSysEnvVers, &theWorld ); PROPASSERT( noErr == oserr ); if( noErr == oserr ) { _pph->dwOSVer = MAKEPSVER( OSKIND_MACINTOSH, HIBYTE(theWorld.systemVersion), // Major LOBYTE(theWorld.systemVersion) );// Minor } else { _pph->dwOSVer = MAKEPSVER( OSKIND_MACINTOSH, 0, 0 ); } } #elif defined(IPROPERTY_DLL) || !defined(WINNT) { // Get the Windows version. DWORD dwWinVersion = GetVersion(); // Use it to set the OSVersion _pph->dwOSVer = MAKEPSVER( OSKIND_WIN32, LOBYTE(LOWORD( dwWinVersion )), // Major HIBYTE(LOWORD( dwWinVersion )) ); // Minor } #else // #if defined(_MAC) ... #elif defined(IPROPERTY_DLL) // Since we're part of the system, we can hard-code the OSVersion, // and save the expense of an API call. _pph->dwOSVer = PROPSETVER_CURRENT; #endif // #if defined(_MAC) ... #elif ... #else // Initialize the rest of the header. _pph->wByteOrder = 0xfffe; //_pph->wFormat = 0; // RtlZeroMemory does this PROPASSERT(_pph->wFormat == 0); // The behavior property is only supported in version-1 property sets. if( 0 != _grfBehavior ) _pph->wFormat = PROPSET_WFORMAT_BEHAVIOR; 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; IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::_LoadHeader" ); propTraceParameters(( "pfmtid=%s, Mode=%d", (NULL == pfmtid)?"":static_cast(CStringize(*pfmtid)), Mode )); 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) { propDbg(( DEB_ITRACE, "_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); // Fix a Visio problem where the User-Defined property set isn't // aligned properly. _FixUnalignedUDPropSet( &cbstm, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; // Compute the minimum size of this property set, as specified // by the property set header and the section headers. This call // will fail if any part of these headers is beyond the end of the // the stream (as determined from cbstm). // It will *not* fail if a section's cbSection indicates that the // section goes beyond the end of the stream (in order to maintain // compatibility with other propset imps, such as Publisher). We'll // handle that later in the _Fix* routines called below. cbMin = _ComputeMinimumSize(cbstm, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; // The following assert should be valid, but it isn't for some // older property sets which we fix in the _Fix* routines, which // are called below. //PROPASSERT(cbMin <= cbstm); propDbg(( DEB_ITRACE, "ComputeMinimumSize: cbMin=%l" szX " cbstm=%l" szX " cbUnused=%l" szX "\n", cbMin, cbstm, cbstm - cbMin )); _oSection = 0; _cSection = 1; _cbTail = 0; #ifdef KERNEL _CodePage = CP_WINUNICODE; #endif if (_HasPropHeader()) // I.e. if not in kernel mode { // 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 > 1 || _pph->reserved < 1) { _cSection = 0; // Mark property set invalid propDbg(( DEB_ERROR, "_LoadHeader: %s\n", cbstm == 0? "Empty Stream" : cbstm < CB_PROPERTYSETHEADER + CB_FORMATIDOFFSET? "Stream too small for header" : _pph->wByteOrder != 0xfffe? "Bad wByteOrder field" : _pph->wFormat > 1 ? "Bad wFormat field" : _pph->reserved < 1? "Bad reserved field" : "Bad dwOSVer field" )); 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) { propDbg(( DEB_ERROR, "Not DocumentSummaryInfo 1st FMTID\n" )); goto Exit; } // Verify that this property set has two sections, and that // the second section is the UD propset. // Note that this gets pfo pointing to the correct entry in the // FMTID/Offset array. 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; // Could be first or second section, depending on pfo } // if (_HasPropHeader()) psh = _GetSectionHeader(); // Scan the property set for code page and behavior properties. // Sets _CodePage and _grfBehavior. _SearchForCodePage( pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; // Validate that the Behavior property only has bits we understand. if( 0 != _grfBehavior && _pph->wFormat < PROPSET_WFORMAT_BEHAVIOR ) { propDbg(( DEB_ITRACE, "_LoadHeader: invalid format for behavior (%d,%x)\n", _pph->wFormat, _grfBehavior )); goto Exit; } else if( ~PROPSET_BEHAVIOR_CASE_SENSITIVE & _grfBehavior ) { propDbg(( DEB_ITRACE, "_LoadHeader: unsupported behavior (%x)\n", _grfBehavior )); goto Exit; } PROPASSERT( 0 == _grfBehavior || _pph->wFormat > 0 ); // 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); propDbg(( DEB_ITRACE, "_LoadHeader: cbTail=%x\n", _cbTail)); } // Fix all header-related problems in the in-memory representation. // The only header-related problems we fix are with SummaryInformation // property sets. if (fSummaryInformation) { _FixSummaryInformation(&cbstm, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; // The stream may have been re-mapped, invalidating our section header pointer psh = _GetSectionHeader(); } // Now that, to the best of our ability, the headers are good, // let's validate them against the actual stream size. if (cbstm < _oSection || 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; propDbg(( DEB_ITRACE, "_LoadHeader: too small for section\n")); goto Exit; } if( 1 < _cSection ) { PROPERTYSECTIONHEADER *psh1; ULONG oSection1 = 0; oSection1 = _GetFormatidOffset(1)->dwOffset; psh1 = _GetSectionHeader( 1, pstatus ); // Validates that it can read up to the header if( !NT_SUCCESS(*pstatus) ) goto Exit; if (cbstm < oSection1 || cbstm < oSection1 + CB_PROPERTYSECTIONHEADER || psh1->cbSection < CB_PROPERTYSECTIONHEADER + psh1->cProperties * CB_PROPERTYIDOFFSET || cbstm < oSection1 + CB_PROPERTYSECTIONHEADER + psh1->cProperties * CB_PROPERTYIDOFFSET || cbstm < oSection1 + psh1->cbSection) { _cSection = 0; propDbg(( DEB_ITRACE, "_LoadHeader: too small for section\n")); goto Exit; } } // Now we know the headers are OK, so let's see if there are any // problems in the properties themselves that we know how // to fix. if (fSummaryInformation || (_State & CPSS_DOCUMENTSUMMARYINFO)) { psh = NULL; // May get re-mapped by _FixPackedPropertySet _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). // // We don't assume anything about the validity of the content // of the section here, as this routine is called // before _LoadHeader completes (we do assume, though, that // the property set header is valid). // // Arguments: [pcbstm] - The size of the mapped stream. This may // be updated by this routine. // [pstatus] - Pointer to NTSTATUS code. // // Returns: None // //--------------------------------------------------------------------------- VOID CPropertySetStream::_FixSummaryInformation(IN OUT ULONG *pcbstm, OUT NTSTATUS *pstatus) { PROPERTYSECTIONHEADER *psh; PROPERTYIDOFFSET *ppo, *ppoMax; *pstatus = STATUS_SUCCESS; IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::_FixSummaryInformation" ); propTraceParameters(( "pcbstm=%p(%d)", pcbstm, *pcbstm )); // If this property set has multiple sections, then it's not one // of the ones we know how to fix in this routine. if (1 != _cSection) goto Exit; // Load pointers to the section header and the PID/Offset array. psh = _LoadPropertyOffsetPointers(&ppo, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) || NULL == psh ) goto Exit; // Ensure that the section header fits within the stream if( (UINT_PTR) ppoMax - (UINT_PTR) ppo > *pcbstm ) { // It's a corrupt property set, but we'll let the normal // corruption detection code catch it. For now, this isn't // what we're looking for, so just move on. 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. if (*pcbstm == _oSection + psh->cbSection + sizeof(ULONG)) { // Look for the thumbnail property. for ( ; ppo < ppoMax; ppo++) { if (ppo->propid == PIDSI_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); // Ensure this property, plus the part of the value we're going to read, // is within the stream if( ppo->dwOffset + CB_SERIALIZEDPROPERTYVALUE // The VT + 2 * sizeof(ULONG) // We need to check the first 8 bytes > *pcbstm ) { // This isn't what we're looking for break; } // 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); } else { // We don't need to adjust the size of the stream, but we // do need to write to it (below to correct the section size). // Until we call SetSize once, we don't know if the mapped stream // is writable, so we'll call SetSize here to ensure that it is // writable. _MSTM(SetSize)(*pcbstm, // Reset the same 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; } // Now correct the section size. propDbg(( DEB_ITRACE, "_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 (cbstm == _oSection + psh->cbSection + sizeof(ULONG)) // Look for the Excel 5.0a problem. // Excel 5.0a set the cbSection field to be 4 bytes too // high (fixed in 5.0b). This code handles the more general case where the // cbSection is too long for the stream. In such cases, if // all the properties actually fit within the stream, the // cbSection field is corrected. if (*pcbstm < _oSection + psh->cbSection) { // We'll fix this problem by adjusting the cbSection // value. We have to be careful, though, // that the entire section fits within this new cbSection // value. For efficiency, we'll just find the property // which is at the highest offset, and verify that it's // within the new section size. // Get what we think is the actual section length. ULONG cbSectionActual = *pcbstm - _oSection; ULONG dwHighestOffset = 0; ULONG cbProperty; // Find the property with the highest offset. for ( ; ppo < ppoMax; ppo++) { if( ppo->dwOffset > dwHighestOffset ) dwHighestOffset = ppo->dwOffset; } // How long is this property? Note that the following routine is // careful not to attempt to read beyond the end of the stream. cbProperty = PropertyLengthNoEH( // Pointer to property (SERIALIZEDPROPERTYVALUE *) _MapOffsetToAddress(dwHighestOffset), // Bytes between above ptr & end of stream *pcbstm - _oSection - dwHighestOffset, 0, // Flags pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; // Does this property fit within the section? If so, then fix this // property set. if( dwHighestOffset + DwordAlign(cbProperty) <= cbSectionActual ) { // Until we call SetSize once, we don't know if the mapped stream // is writable, so we'll call SetSize here to ensure that it is. _MSTM(SetSize)(*pcbstm, // Reset the same 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; // Fix the section size psh->cbSection = dwHighestOffset + DwordAlign(cbProperty); } else { StatusCorruption(pstatus, "SumInfo cbSection is too long for the Stream."); } } // if (*pcbstm < _oSection + psh->cbSection) // ---- // Exit // ---- Exit: return; } // CPropertySetStream::_FixSummaryInformation() //+-------------------------------------------------------------------------- // 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. // // Note: this routine assumes that the property set // and section headers are valid (this is verified // already by the caller, _LoadHeader). // // 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; IFDBG( BOOLEAN fExpandDocSummaryInfo = FALSE ); IFDBG( HRESULT &hr = *pstatus ); 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; ULONG cbpropbuf = 0; #endif ULONG cbtotal = 0; // ----- // Begin // ----- propITrace( "CPropertySetStream::_FixPackedPropertySet" ); *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) { propDbg(( DEB_ITRACE, "_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 = reinterpret_cast ( CoTaskMemAlloc( CB_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 = reinterpret_cast ( CoTaskMemAlloc( sizeof(ULONG) *(psh->cProperties + 1) )); if (aopropShrink == NULL) { *pstatus = STATUS_NO_MEMORY; goto Exit; } aopropFinal = reinterpret_cast ( CoTaskMemAlloc( sizeof(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. 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 *) CoTaskMemAlloc( 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; // Validate the property's offset if( ppoT->dwOffset >= psh->cbSection ) { StatusCorruption(pstatus, "Property's offset is too long for the section."); goto Exit; } // 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)) { propDbg(( DEB_ITRACE, "_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 = cbprop; // 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 = PropertyLengthNoEH(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; propDbg(( DEB_ITRACE, "_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; // We may not have a writable mapped stream. Do a SetSize on it // to ensure that we do. _MSTM(SetSize)( cbstm, FALSE, // Not persistent (VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; // Always reload after obtaining a new _pph. psh = _LoadPropertyOffsetPointers(&ppoBase, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; propDbg(( DEB_ITRACE, "_FixPackedPropertySet: cbstm=%x cbdelta=%x cexpand=%x ccompact=%x\n", cbstm, cbdelta, cExpand, cCompact )); // ----------------------------- // Grow the Stream if necessary. // ----------------------------- // If we need to grow the stream, do it now. If we need to shrink the stream, // we'll do it later, after the compaction & expansion phases. if (cbdelta > 0) { propDbg(( DEB_ITRACE, "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 forward. 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 = (LONG)(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). propDbg(( DEB_ITRACE, "_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 = poprop[1] - poprop[0]; 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) { propDbg(( DEB_ITRACE, "_FixPackedPropertySet: Patch propid %x offset=%x->%x\n", ppo->propid, ppoT->dwOffset, ppo->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: CoTaskMemFree( apoT ); CoTaskMemFree( aopropShrink ); CoTaskMemFree( aopropFinal ); #if i386 == 0 CoTaskMemFree( (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] -- in: length of buffer at pprop // out: 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, IN OUT ULONG *pcbprop) { PROPASSERT( PatchOp == PATCHOP_COMPUTESIZE || PatchOp == PATCHOP_ALIGNLENGTHS || PatchOp == PATCHOP_EXPAND); PROPASSERT(pprop != NULL); PROPASSERT(pcbprop != NULL); ULONG cbpropIn = *pcbprop; // Ensure we'll be able to read what we need if( cbpropIn < sizeof(pprop->dwType) + sizeof(DWORD) ) // Not a recognizable docparts vector (probably corrupt) return FALSE; cbpropIn -= sizeof(pprop->dwType) + sizeof(DWORD); // We're looking for a vector of Ansi strings if ( PropByteSwap(pprop->dwType) == (VT_VECTOR | VT_LPSTR) && _CodePage != CP_WINUNICODE) { ULONG cString; VOID *pv; // How many strings? cString = PropByteSwap( *(DWORD *) pprop->rgb ); // Point to the start of the strings pv = Add2Ptr(pprop->rgb, sizeof(DWORD)); // Fix the elements in this vector if (_FixDocPartsElements(PatchOp, cString, pv, pv, &cbpropIn)) { *pcbprop = cbpropIn + CB_SERIALIZEDPROPERTYVALUE + sizeof(ULONG); return(TRUE); } } return(FALSE); // Not a recognizable DocParts vector } //+-------------------------------------------------------------------------- // Member: CPropertySetStream::_FixDocPartsElements // // Synopsis: Iteratively 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] -- in: size of buffer at pvSrc // out: 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, IN OUT ULONG *pcbprop) { ULONG cb = 0; PROPERTYSECTIONHEADER UNALIGNED *psh = _GetSectionHeader(); ULONG cbpropIn = *pcbprop; const ULONG cStringMax = cString; BOOLEAN bRet = FALSE; 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); } ULONG* acb = (ULONG *) CoTaskMemAlloc(cStringMax * sizeof(ULONG)); if (NULL == acb) { return FALSE; } while (TRUE) { if (cString == 0) { cbpropIn = 0; bRet = TRUE; break; } // Get the cb and validate the source. If it's too long, return false, // and let the normal corruption-detection code deal with it. if( cbpropIn < sizeof(DWORD) ) { bRet = FALSE; break; } cb = sizeof(DWORD) + PropByteSwap( *(DWORD UNALIGNED *) pvSrc ); if( cbpropIn < cb ) { bRet = FALSE; break; } cbpropIn -= cb; // 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 } cString = cString - 1; pvDst = Add2Ptr(pvDst, DwordAlign(cb)); pvSrc = (VOID UNALIGNED const *)Add2ConstPtr(pvSrc, cb); acb[cString] = cb; // save length so we can loop backwards } // while loop if (bRet == TRUE) { while (cString < cStringMax) { cb = acb[cString]; // get the previous length pvDst = Add2Ptr(pvDst, -(LONG)DwordAlign(cb)); pvSrc = (VOID UNALIGNED const *)Add2ConstPtr(pvSrc, -(LONG)cb); cbpropIn = cbpropIn + DwordAlign(cb); if (PatchOp == PATCHOP_EXPAND) { // By the time we're called for expansion, the caller has ensured // that we have enough space at pvDst to do this move/zero without // overrunning someone's stack/heap/etc. 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((void const *)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))); *(ULONG *) pvDst = PropByteSwap( (ULONG) DwordAlign( PropByteSwap( *(ULONG *) pvDst ))); } cString++; // proceed to next element } } *pcbprop = cbpropIn; if (acb != NULL) CoTaskMemFree (acb); return bRet; } //+-------------------------------------------------------------------------- // 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] -- in: size of buffer at pprop // out: 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, IN OUT ULONG *pcbprop) { ULONG celem; ULONG cbpropIn = *pcbprop; PROPASSERT( PatchOp == PATCHOP_COMPUTESIZE || PatchOp == PATCHOP_ALIGNLENGTHS || PatchOp == PATCHOP_EXPAND); PROPASSERT(pprop != NULL); PROPASSERT(pcbprop != NULL); // Ensure we have enough room in pprop to get the element count if( cbpropIn < CB_SERIALIZEDPROPERTYVALUE + sizeof(ULONG) ) // Not what we're looking for (and corrupt, but we'll let the standard // code deal with that). return FALSE; // If the property is a variant vector, and // there are an even number of elements, and // the property set is Ansi ... celem = PropByteSwap(*(ULONG *) pprop->rgb); if( PropByteSwap(pprop->dwType) == (VT_VECTOR | VT_VARIANT) && (celem & 1) == 0 && _CodePage != CP_WINUNICODE) { pprop = (SERIALIZEDPROPERTYVALUE *) Add2Ptr(pprop->rgb, sizeof(ULONG)); cbpropIn -= CB_SERIALIZEDPROPERTYVALUE + sizeof(ULONG); if (_FixHeadingPairElements(PatchOp, celem/2, pprop, pprop, &cbpropIn)) { *pcbprop = cbpropIn + 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] -- in: size of buffer at ppropSrc // out: 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, IN OUT ULONG *pcbprop) { ULONG cbpropIn = *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); } // Ensure we can at least look at the type and length if( cbpropIn < CB_SERIALIZEDPROPERTYVALUE + sizeof(ULONG) ) return FALSE; // If the first element of the pair is a VT_LPSTR, ... if( PropByteSwap(ppropSrc->dwType) == VT_LPSTR ) { ULONG cb; // Compute size of the string element. cb = CB_SERIALIZEDPROPERTYVALUE + sizeof(ULONG) + PropByteSwap( *(DWORD UNALIGNED *) ppropSrc->rgb ); // Ensure we can read all of this if( cbpropIn < cb ) return FALSE; // 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 } // We're might increment cb next, but first ensure we have that much room if( cbpropIn < cb + CB_SERIALIZEDPROPERTYVALUE + sizeof(DWORD) ) return FALSE; // So the first element was a VT_LPSTR, which is part of what we're looking for. // Now, we also want the second element of the pair to be a VT_I4, ... if ( PropByteSwap( (DWORD) VT_I4 ) == ( (SERIALIZEDPROPERTYVALUE UNALIGNED const *) Add2ConstPtr(ppropSrc, cb) )->dwType ) { // So far, this looks like the special-case property we're after // (the first pair is an lpstr/i4). Make a recursive call to see // if the remainder of the vector is the same (and depending on PatchOp, // actually fix up the remainder). cb += CB_SERIALIZEDPROPERTYVALUE + sizeof(DWORD); cbpropIn -= cb; if (_FixHeadingPairElements( PatchOp, cPairs - 1, (SERIALIZEDPROPERTYVALUE *) Add2Ptr(ppropDst, DwordAlign(cb)), (SERIALIZEDPROPERTYVALUE UNALIGNED const *) Add2ConstPtr(ppropSrc, cb), &cbpropIn )) { // Yes, the remainder of the vector matched what we're looking for, // and depending on PatchOp it may be already fixed. In any case, // cbpropIn was updated to show its total size, so now let's show // the total size including this current pair. *pcbprop = cbpropIn + DwordAlign(cb); // Are we actually doing an expansion (as opposed to just calcing the 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. // // If we're doing an expand, we know that the src and dst buffers // are sufficiently large. // 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. *(ULONG *) ppropDst->rgb = PropByteSwap( (ULONG) 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); IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::QueryPropertySet" ); propTraceParameters(( "pspss=%p", pspss )); 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); IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::SetClassId" ); propTraceParameters(( "clsid=%s", static_cast(CStringize(*pclsid)) )); if (IsReadOnlyPropertySet(_Flags, _State)) { StatusAccessDenied(pstatus, "SetClassId: deleted or read-only"); goto Exit; } _SetModified(pstatus); if( !NT_SUCCESS(*pstatus) ) { TraceStatus("SetClassId: couldn't SetModified"); goto Exit; } _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); IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::EnumeratePropids" ); propTraceParameters(( "pkey=%p(%d), pcprop=%p(%d), apropids=%p", pkey,*pkey, pcprop,&pcprop, apropids )); if (_State & CPSS_USERDEFINEDDELETED) { StatusAccessDenied(pstatus, "EnumeratePropids: deleted"); goto Exit; } if (_LoadPropertyOffsetPointers(&ppoStart, &ppoMax, pstatus) == NULL) { // Either there's no section, or there was an error if( !NT_SUCCESS(*pstatus) ) goto Exit; } else { // If the caller passed in the phKey cookie (it's really a propid), // move ppoStart up to the point just beyond it. if (propidPrev != 0) { for (ppo = ppoStart; ppo < ppoMax; ppo++) { if (ppo->propid == propidPrev) { ppoStart = ++ppo; break; } } } // Now, starting a ppoStart, loop through the remaining // propids, until we find one that we can return // (we don't return, e.g. the propid for the dicitionary). for (ppo = ppoStart; ppo < ppoMax; ppo++) { if (ppo->propid != PID_DICTIONARY && ppo->propid != PID_CODEPAGE && ppo->propid != PID_LOCALE && ppo->propid != PID_BEHAVIOR) { 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 = NULL; *pstatus = STATUS_SUCCESS; PROPASSERT(_IsMapped()); if (_cSection != 0) { psh = _GetSectionHeader(); ULONG cbstm = _MSTM(GetSize)(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; // Ensure that we can read all of the PID/Offset // table. if (cbstm < _oSection || cbstm < _oSection + CB_PROPERTYSECTIONHEADER || cbstm < _oSection + CB_PROPERTYSECTIONHEADER + psh->cProperties * CB_PROPERTYIDOFFSET) { StatusCorruption(pstatus, "LoadPropertyOffsetPointers: stream size"); goto Exit; } *pppo = psh->rgprop; *pppoMax = psh->rgprop + psh->cProperties; } // ---- // Exit // ---- Exit: if( !NT_SUCCESS(*pstatus) ) psh = NULL; else if( NULL == psh ) // This should never happen. *pstatus = STATUS_INTERNAL_DB_CORRUPTION; 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; // Get a pointer to the section header, and to the beginning // and end of the propid/offset array. psh = _LoadPropertyOffsetPointers(&ppoBase, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; // Do we have a section? if (psh != NULL) { // Loop through the propid/offset array in search of the // target propid. for (ppo = ppoBase; ppo < ppoMax; ppo++) { // Verify that this offset is reasonable, and that the // property value's type is within the section size. if (IsDwordAligned(ppo->dwOffset) && ppo->dwOffset >= CB_PROPERTYSECTIONHEADER + psh->cProperties * CB_PROPERTYIDOFFSET && psh->cbSection >= ppo->dwOffset + CB_SERIALIZEDPROPERTYVALUE) { // If this isn't the propid we're looking for, move on. if (ppo->propid != propid) { // Nope - move on. continue; } // Yes, this is the property we're looking for. Point // pprop to the serialized value. pprop = (SERIALIZEDPROPERTYVALUE *) _MapOffsetToAddress(ppo->dwOffset); // Does the caller want to know the serialized size of this property? if (pcbprop != NULL) { // Yes, we need to figure out the size. ULONG cb; // The size can't possibly be bigger than what's left of the section // after the start of this property. cb = psh->cbSection - ppo->dwOffset; // The dictionary property requires special-case handling. if (propid == PID_DICTIONARY) { *pcbprop = _DictionaryLength( (DICTIONARY const *) pprop, cb, // Bytes available at pprop pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; } // Otherwise, just use PropertyLength to figure it out. else { // cb shows bytes available at pprop *pcbprop = PropertyLengthNoEH(pprop, cb, 0, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; } } // if (pcbprop != NULL) if (pcbprop == NULL || psh->cbSection >= ppo->dwOffset + *pcbprop) { // Success goto Exit; } } // if (IsDwordAligned(ppo->dwOffset) 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); IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::GetValue" ); propTraceParameters(( "propid=0x%x, pcbprop=%p", propid, pcbprop )); pprop = NULL; // 2nd section hack if (_State & CPSS_USERDEFINEDDELETED) { StatusAccessDenied(pstatus, "GetValue: deleted"); goto Exit; } // You can't read the dictionary directly if (propid == PID_DICTIONARY) { DebugTrace(0, DEBTRACE_ERROR, ("GetValue: PID_DICTIONARY\n")); StatusInvalidParameter(pstatus, "GetValue: PID_DICTIONARY"); goto Exit; } // You also can't read the special security & modify-time properties. if (propid == PID_SECURITY || propid == PID_MODIFY_TIME) { StatusError( pstatus, "PID_SECURITY, PID_MODIFY_TIME not supported", STATUS_NOT_SUPPORTED ); } else { // So this is a valid property to read, get it into pprop. 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) { propDbg(( DEB_ITRACE, "GetValue: propid=%lx pprop=NULL\n", propid )); } else { char valbuf[CB_VALUESTRING]; propDbg(( DEB_ITRACE, "GetValue: propid=%lx pprop=%l" szX " vt=%hx val=%s cb=%l" szX "\n", propid, _MapAddressToOffset(pprop), PropByteSwap( pprop->dwType ), ValueToString(pprop, *pcbprop, valbuf, sizeof(valbuf)), *pcbprop )); } #endif // ---- // Exit // ---- Exit: if( STATUS_NOT_SUPPORTED == *pstatus ) propSuppressExitErrors(); return(pprop); } //+-------------------------------------------------------------------------- // Member: CPropertySetStream::SetValue // // Synopsis: update/add/delete property values // // Arguments: [cprop] -- count of properties // [pip] -- pointer to indirect indexes // [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, OPTIONAL IN OUT INDIRECTPROPERTY **ppip, IN PROPVARIANT const avar[], IN PROPERTY_INFORMATION *apinfo, OUT OPTIONAL USHORT *pCodePage, 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; int cIndirect = 0; CStreamChunk *pscnk0 = NULL; ULONG cbNewSize; USHORT NewCodePage = _CodePage; // ---------- // Initialize // ---------- PROPASSERT(PROPSET_BYTEORDER == _pph->wByteOrder); *pstatus = STATUS_SUCCESS; IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::SetValue" ); propTraceParameters(( "cprop=%d, ppip=%p, avar=%p, apinfo=%p, pCodePage=%p", cprop, ppip, avar, apinfo, pCodePage )); // 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 : 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(pstatus); if( !NT_SUCCESS(*pstatus) ) { TraceStatus( "SetValue: couldn't SetModified" ); goto Exit; } 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 // ------------------------ // Loop through the properties in caller-provided array. // // 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 = 0; SERIALIZEDPROPERTYVALUE const *pprop = NULL; PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail); // Is this PROPID read-only? E.g. the code page is a read-only property, // even if the property set is opened read/write. if (IsReadOnlyPropid(apinfo[iprop].pid)) { BOOL fWritable = FALSE; // Read-only properties aren't settable except for two special // cases: (1) we get called by the SetPropertyNames method // to create an empty dictionary; and (2) it's OK to set the // codepage/LCID on an empty property set. // Is this the SetPropertyNames case? if (cprop == 1 && apinfo[0].pid == PID_DICTIONARY && apinfo[0].cbprop != 0 && ( avar != NULL && avar[0].vt == VT_DICTIONARY ) ) { fWritable = TRUE; } // Or, is this a codepage/lcid going into an empty property set? else if( IsLocalizationPropid(apinfo[iprop].pid) ) { // These properties may only be written as singletons, or // together, to an empty property set. So we should be at // an iprop of 0 or 1. We'll do all the checking at 0. if( 0 == iprop ) { // First, is this property set empty? BOOLEAN fSettable = _IsLocalizationSettable(pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; if( fSettable ) { // It's OK to set these props, so as long as either this // is the only property being written, or there's one // more and it's a localization property too, we're OK. if( 1 == cprop ) fWritable = TRUE; else if( 2 == cprop && IsLocalizationPropid(apinfo[1].pid) ) fWritable = TRUE; } } else if( 1 == iprop ) { // This is valid iff iprop==0 was a localization property, and therefore // we did the checking already. if( IsLocalizationPropid(apinfo[0].pid) ) fWritable = TRUE; } } // else if( IsLocalizationPropid(apinfo[iprop].pid) ) // If we didn't just find an exception to the read-only rule, // then this is invalid. if( !fWritable ) { propDbg(( DEB_ITRACE, "SetValue: read-only propid=%lx\n", apinfo[iprop].pid )); StatusInvalidParameter(pstatus, "SetValue: read-only PROPID"); goto Exit; } // Validate the codepage type, and put it into the NewCodePage local. if( PID_CODEPAGE == apinfo[iprop].pid && NULL != avar ) { if( VT_I2 != avar[iprop].vt ) { propDbg(( DEB_ERROR, "SetValue: invalid type for codepage (%d)\n", avar[iprop].vt )); StatusInvalidParameter(pstatus, "SetValue: invalid type for CodePage"); goto Exit; } NewCodePage = avar[iprop].iVal; } // Validate the locale type. else if( PID_LOCALE == apinfo[iprop].pid && NULL != avar ) { if( VT_UI4 != avar[iprop].vt ) { propDbg(( DEB_ERROR, "SetValue: invalid type for locale ID (%d)\n", avar[iprop].vt )); StatusInvalidParameter(pstatus, "SetValue: invalid type for LCID"); goto Exit; } } } // if (IsReadOnlyPropid(apinfo[iprop].pid)) // When we get to this point, we have a writeable property. // Get a pointer to the serialized property value (if this property // doesn't already exist, pprop will be NULL). 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 input array, ignore it. // I.e., the caller passed in an array, and for some reason wrote // the same propid multiple times, so we'll ignore all but the last. for (i = iprop + 1; i < cprop; i++) { if (apinfo[i].pid == apinfo[iprop].pid) { // Ignore this one and wait for it to show // up again. #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); // Validate that the property value is somewhat sane (this is checked // in detail at the appropriate times, but this check ensures we don't // having any 32 bit rounding/wrapping errors). if( apinfo[iprop].cbprop > CBMAXPROPSETSTREAM ) { StatusDiskFull(pstatus, "SetValue: invalid type for LCID"); goto Exit; } // Are we overwriting a property that already exists? if (pprop != NULL) { // Yes, we're either deleting or overwriting an existing property. ULONG cbPropNew; PROPASSERT(apinfo[iprop].pid != PID_DICTIONARY); // A cbprop of 0 indicates that we should delete this property if (apinfo[iprop].cbprop == 0) { propDbg(( DEB_ITRACE, "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, sizeof(valbuf)), cbPropOld)); cbPropNew = 0; cDelete++; apinfo[iprop].operation = PROPOP_DELETE; } // if (apinfo[iprop].cbprop == 0) // Otherwise, we're writing this property to the property set else { /* propDbg(( DEB_ITRACE, "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 ), avar[iprop].vt, cbPropOld, apinfo[iprop].cbprop, ValueToString(pprop, cbPropOld, valbuf), VariantToString( avar[iprop], varbuf, sizeof( varbuf )) )); */ cbPropNew = apinfo[iprop].cbprop; // If this property is a different size than the existing value, // then we'll write it to the end. if (cbPropOld != cbPropNew) { cbInsertMove += apinfo[iprop].cbprop; cMove++; apinfo[iprop].operation = PROPOP_MOVE; } // Otherwise, we'll update the property value in place. else { cUpdate++; apinfo[iprop].operation = PROPOP_UPDATE; } } // // if (apinfo[iprop].cbprop == 0) ... else // If we're deleting or moving, update the chunk list. 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; } // if (pprop != NULL) // Or, are we deleting a non-extant property? else if (apinfo[iprop].cbprop == 0) { // The request is to delete the property, but a NULL pprop indicates // that the property doesn't exist. We'll ignore this part of the request. #if DBGPROP cIgnore++; #endif PROPASSERT(apinfo[iprop].pid != PID_DICTIONARY); apinfo[iprop].operation = PROPOP_IGNORE; } // Otherwise, we're inserting a new property else { /* propDbg(( DEB_ITRACE, "SetValue: Inserting new propid=%lx vt=%hx " "cbNew=%l" szX " val=%s\n", apinfo[iprop].pid, avar[iprop].vt, apinfo[iprop].cbprop, 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; } // if (pprop != NULL) ... else if ... else PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail); // In order to delete any old stream or storage type properties // we count the properties which used to be VT_STREAM etc. // Also, we count the properties which are to be created as // streams or storages. if (ppip != NULL && apinfo[iprop].operation != PROPOP_IGNORE) { if ((pprop != NULL && IsIndirectVarType(PropByteSwap(pprop->dwType))) || (avar != NULL && IsIndirectVarType(avar[iprop].vt))) { cIndirect++; } } } // if (i == cprop) } // for (iprop = 0; iprop < cprop; iprop++) // We're now done classifying each of the properties to be added. // ------------------------------------------------------------ // Put existing, to-be-overwritten, indirect properties in ppip // ------------------------------------------------------------ // Did the caller give us an INDIRECTPROPERTY buffer, and are // there indirect properties being added and/or overwritten? if (ppip != NULL && cIndirect != 0) { // allocate needed space for indirect information INDIRECTPROPERTY *pipUse; // If cprop is 1, the caller-provided ppip is a pointer to an INDIRECTPROPERTY // that we can use. Otherwise, we need to allocate space for it, and put // it in *ppip for the caller. if (cprop != 1) { pipUse = *ppip = reinterpret_cast ( CoTaskMemAlloc( sizeof(INDIRECTPROPERTY) * (cIndirect + 1) )); if (*ppip == NULL) { StatusNoMemory(pstatus, "SetValue: Indirect Name"); goto Exit; } RtlZeroMemory( pipUse, sizeof(INDIRECTPROPERTY) * (cIndirect + 1) ); pipUse[cIndirect].Index = MAXULONG; } else { pipUse = (INDIRECTPROPERTY *) ppip; RtlZeroMemory( pipUse, sizeof(*pipUse) ); } // Loop through the properties in the input array, and put info about the // indirects (non-simple) being added or overrritten into the array at pipUse. int iIndirect = 0; for (iprop = 0; iprop < cprop; iprop++) { ULONG cbPropOld; SERIALIZEDPROPERTYVALUE const *pprop = NULL; PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail); if (apinfo[iprop].operation == PROPOP_IGNORE || apinfo[iprop].pid == PID_ILLEGAL) { continue; } // Get the existing serialized property value, if there is one // (if there isn't, pprop will be NULL, but *pstatus will be success). pprop = _LoadProperty(apinfo[iprop].pid, &cbPropOld, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; PROPASSERT(cbstm == _oSection + psh->cbSection + _cbTail); // If the property already exist, and it's indirect type, we need to load // pipUse with the indirect name so that the caller can delete it. if (pprop != NULL && IsIndirectVarType(PropByteSwap(pprop->dwType))) { CHAR *pszName; ULONG cbName; BOOL fAlloc = FALSE; // Did we alloc pszName? // Point to the indirect property inline value (i.e. the name of the // stream/storage which holds it). if( VT_VERSIONED_STREAM == PropByteSwap(pprop->dwType) ) { if( cbPropOld < sizeof(GUID) + sizeof(ULONG) ) { StatusError(pstatus, "SetValue: corrupt versioned_stream guid", STATUS_INTERNAL_DB_CORRUPTION); goto Exit; } // Before the value is a GUID and the string length. cbPropOld -= sizeof(GUID) + sizeof(ULONG); pszName = (CHAR *) Add2ConstPtr(pprop->rgb, sizeof(GUID) ); cbName = *(ULONG*) pszName; pszName = (CHAR *) Add2ConstPtr(pszName, sizeof(ULONG) ); } else { if( cbPropOld < 2*sizeof(ULONG) ) { StatusError(pstatus, "SetValue: corrupt indirect string", STATUS_INTERNAL_DB_CORRUPTION); goto Exit; } cbPropOld -= 2 * sizeof(ULONG); // Subtract size of VT & length cbName = *(ULONG*) pprop->rgb; pszName = (CHAR *) Add2ConstPtr(pprop->rgb, sizeof(ULONG)); // Move past length } // Validate the string is terminated properly. _LoadProperty // already ensured that the property is readable and that the length // field makes sense (fits within the section). if( CP_WINUNICODE == _CodePage ) { WCHAR *pwszName = (WCHAR*) pszName; if( L'\0' != pwszName[ cbName/sizeof(WCHAR) - 1 ] ) { StatusError(pstatus, "SetValue: corrupt indirect string", STATUS_INTERNAL_DB_CORRUPTION); goto Exit; } } else { if( '\0' != pszName[ cbName - 1 ] ) { StatusError(pstatus, "SetValue: corrupt indirect string", STATUS_INTERNAL_DB_CORRUPTION); goto Exit; } } // Do we need to convert the name between Ansi & Unicode? if (_CodePage != CP_WINUNICODE // Ansi propset && OLECHAR_IS_UNICODE) // Unicode OLE APIs { // Convert the indirect reference to Unicode PrpConvertToUnicode( pszName, cbName, _CodePage, (WCHAR **) &pszName, &cbName, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; fAlloc = TRUE; // We need to free pszName } else if (_CodePage == CP_WINUNICODE // Unicode propset && !OLECHAR_IS_UNICODE ) // Ansi OLE APIs { // Byte-Swap the Unicode indirect reference value WCHAR *pwszBuffer = NULL; // After this call, the appropriately swapped name will be // in pszName. If an alloc was required, pszBuffer will point // to the new buffer (we must free this). PBSInPlaceAlloc( (WCHAR**) &pszName, &pwszBuffer, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; // Convert the reference value to Ansi. PrpConvertToMultiByte( (WCHAR*) pszName, cbName, CP_ACP, (CHAR **) &pszName, &cbName, pstatus); CoTaskMemFree( pwszBuffer ); if( !NT_SUCCESS(*pstatus) ) goto Exit; fAlloc = TRUE; // We need to free pszName } pipUse[iIndirect].poszName = reinterpret_cast( CoTaskMemAlloc( cbName )); if (pipUse[iIndirect].poszName == NULL) { StatusNoMemory(pstatus, "SetValue: Indirect Name2"); goto Exit; } RtlCopyMemory( pipUse[iIndirect].poszName, pszName, cbName); // Is byte-swapping necessary? It is if the property set // codepage is Unicode, and if OLECHARs are also Unicode. // If both are Ansi, then no byte-swapping is ever necessary, // and if one is Ansi and the other is Unicode, then we // already byte-swapped above during the conversion. if (_CodePage == CP_WINUNICODE && OLECHAR_IS_UNICODE ) { // Convert from propset-endian to system-endian. PBSBuffer( pipUse[iIndirect].poszName, cbName, sizeof(OLECHAR) ); } // Clean up pszName if( fAlloc ) { // In the Unicode/MBCS conversions, we did an alloc which // we must free now. PROPASSERT(pszName != NULL); PROPASSERT( pszName != (CHAR *) Add2ConstPtr(pprop->rgb, sizeof(ULONG))); CoTaskMemFree( pszName ); } } // if (pprop != NULL && IsIndirectVarType(PropByteSwap(pprop->dwType))) else if( avar == NULL || !IsIndirectVarType(avar[iprop].vt) ) { // Neither the property being overwritten, nor the property // being written is indirect, so we can continue on to // check the next property (skipping the pipUse updating // below). continue; } // If we get here, we know that either this property is // an indirect type, or it's overwriting an indirect property (or both). // We established pipUse[].pszName above, so we just need to // insert the index and move on. pipUse[iIndirect].Index = iprop; iIndirect++; } // for (iprop = 0; iprop < cprop; iprop++) PROPASSERT(iIndirect == cIndirect); } // if (ppip != NULL && cIndirect != 0) propDbg(( DEB_ITRACE, "SetValue: Total Props %l" szX "\n", cprop )); propDbg(( DEB_ITRACE, "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); // -------------------------------------------------------- // Calculate the total size adjustments to the property set // -------------------------------------------------------- // 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. // However, 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; } propDbg(( DEB_ITRACE, "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); } } // if (cbChange > 0) // From this point on, the operation should succeed. // If necessary, the stream has already been grown. // ---------------------------------------- // Write the new properties into the stream // ---------------------------------------- // Update the PID/Offset table, and compact the stream, creating a whole at the // end of the stream for the new property values. 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; ULONG cbprop; ULONG cIndirectProps; 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 { // Serialize the PROPVARIANT in avar // directly into the mapped stream. We ask for the // count of indirect properties, even though we don't // use it, in order to tell the routine that we // can handle them. Any handling that is actually // required must be handled by our caller. WORD wMinFormatRequired = 0; // (This cbprop was actually originally calced by // StgConvertVariantToPropertyNoEH) cbprop = apinfo[iprop].cbprop; pprop = StgConvertVariantToPropertyNoEH( &avar[iprop], _CodePage, pprop, &cbprop, apinfo[iprop].pid, FALSE, FALSE, &cIndirectProps, &wMinFormatRequired, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; // This property type might not have been supported in the original // property set format. If so, it may be necessary to increment // the format in the header. _pph->wFormat = max( _pph->wFormat, wMinFormatRequired ); 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 = cbprop; if (propid == PID_HEADINGPAIR) { _FixHeadingPairVector( PATCHOP_ALIGNLENGTHS, pprop, &cbpropT); } else if (propid == PID_DOCPARTS) { _FixDocPartsVector( PATCHOP_ALIGNLENGTHS, pprop, &cbpropT); } } propDbg(( DEB_ITRACE, "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, sizeof(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. if ( cbChange < 0 ) { propDbg(( DEB_ITRACE, "SetSize(%x) SetValue shrink\n", cbstm + cbChange )); _MSTM(SetSize)(cbstm + cbChange, TRUE, (VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; } // ---- // Exit // ---- if( NULL != pCodePage ) *pCodePage = _CodePage = NewCodePage; Exit: scl.Delete(); if( !NT_SUCCESS(*pstatus) ) { if( ppip != NULL && 0 != cIndirect ) { INDIRECTPROPERTY *pipUse; pipUse = (1 == cprop) ? (INDIRECTPROPERTY*) ppip : *ppip; if( NULL != pipUse ) { for (int iFree = 0; iFree < cIndirect; iFree++) { CoTaskMemFree( pipUse[iFree].poszName ); } } if (cprop != 1) { CoTaskMemFree( pipUse ); *ppip = NULL; } } } // if( !NT_SUCCESS(*pstatus) ) } //+-------------------------------------------------------------------------- // 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 = NULL, *ppoMax = NULL; 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 = NULL, *ppoMax = NULL; // 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 = NULL, *ppoMax = NULL; *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) { #if DBG==1 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)); #endif // DBG==1 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::_FixUnalignedUDPropSet, private // // Synopsis: Fixes a case seen with Visio where the user-defined // property set is not dword aligned. The fix is to align // it (shifting everything else back) in memory. // // Arguments: [*pcbstm] -- in: current stream size // out: updated stream size. // [pstatus] -- pointer to NTSTATUS code // //+-------------------------------------------------------------------------- VOID CPropertySetStream::_FixUnalignedUDPropSet( ULONG *pcbstm, NTSTATUS *pstatus ) { *pstatus = STATUS_SUCCESS; // // Don't assume *any* class variables except _pph & _State are loaded yet! // ULONG cSection; UNALIGNED PROPERTYSECTIONHEADER* pshOld = NULL; PROPERTYSECTIONHEADER* pshNew = NULL; ULONG cbDelta = 0; ULONG cbTail = 0; ULONG oSection = 0; // We only do this fixup for the docsuminfo/userdefined property sets. if( !(_State & (CPSS_USERDEFINEDPROPERTIES|CPSS_DOCUMENTSUMMARYINFO)) ) return; // Make sure we have a header. if( NULL == _pph ) return; // Make sure the stream is at least big enough to have // a second section. if( *pcbstm < CB_PROPERTYSETHEADER + 2*CB_FORMATIDOFFSET ) return; // We're only looking for a 2-section problem, so we're done if // there's only one section. cSection = _pph->reserved; if( 1 >= cSection ) return; // Get the stream-relative offset of the second section. //oSection = _GetFormatidOffset(1)->dwOffset; oSection = ((FORMATIDOFFSET *) Add2Ptr(_pph, sizeof(*_pph)))[1].dwOffset; // If it's already aligned, then we're done. if( IsDwordAligned(oSection) ) return; // Determine how much we need to add to make it aligned, and determine // the size of the stream after the misalignment point. cbDelta = DwordRemain(oSection); pshOld = (PROPERTYSECTIONHEADER *) Add2Ptr(_pph, oSection); pshNew = (PROPERTYSECTIONHEADER *) ( (ULONG_PTR) pshOld + cbDelta ); // ?? This looks to be a convoluted way of saying // cbTail = *pcbstm - oSection cbTail = (ULONG)( (ULONG_PTR) pshOld - (ULONG_PTR) _pph ); cbTail = *pcbstm - cbTail; // Make sure there's enough stream left to see the second section's header. // ?? This looks to be unnecssary (we don't read the section header anywhere), // but harmless so leaving intact to be safe. if( *pcbstm < oSection + sizeof(PROPERTYSECTIONHEADER) ) { StatusCorruption (pstatus, "_FixUnalignedUDPropSet: stream size too short to read section header"); goto Exit; } // Reset the stream size, only in memory for now, to accomodate // cbDelta more bytes. _MSTM(SetSize)( *pcbstm + cbDelta, FALSE, // Not persistent, only in memory (VOID **) &_pph, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; *pcbstm += cbDelta; // Recalc the location of the section header (in memory) now // that we have a new _pph from the _MSTM call. pshOld = (PROPERTYSECTIONHEADER *) Add2Ptr(_pph, oSection); pshNew = (PROPERTYSECTIONHEADER *) ( (ULONG_PTR) pshOld + cbDelta ); // Shift everything out by cbDelta bytes. RtlMoveMemory( pshNew, pshOld, cbTail ); ((FORMATIDOFFSET *) Add2Ptr(_pph, sizeof(*_pph)))[1].dwOffset += cbDelta; *pstatus = STATUS_SUCCESS; Exit: return; } //+-------------------------------------------------------------------------- // 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; // Can we read _pph? if( cbstm < CB_PROPERTYSETHEADER ) { StatusError(pstatus, "SetValue: corrupt versioned_stream guid", STATUS_INTERNAL_DB_CORRUPTION); goto Exit; } // Get the section count, and update cbMin to include everything // up to the first section. 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; // Get the section header. This will validate that it's safe // to read up to and including the section header. 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; } } // The following can't be asserted, because there may be // a correctable reason why cbstm < oMax at in the Open path // (see the Excel 5.0a problem in _FixSummaryInformation) //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; // Ensure we can read pdy->cEntries if( cbbuf < CB_DICTIONARY ) { StatusCorruption(pstatus, "_DictionaryLength: section size"); goto Exit; } // Loop through the dictionary entries for (i = 0, pent = &pdy->rgEntry[0]; i < PropByteSwap( pdy->cEntries ); i++, pent = _NextDictionaryEntry( pent )) { void *pvName = NULL; // Ensure the buffer is big enough to hold this entry // (_DictionaryEntryLength only looks at the // CB_DICTIONARY_ENTRY bytes, so this call is safe). if( cbbuf < cbDict + CB_DICTIONARY_ENTRY ) { StatusCorruption(pstatus, "_DictionaryLength: section size"); goto Exit; } if( cbbuf < (UINT_PTR) pent - (UINT_PTR) pdy + _DictionaryEntryLength( pent )) { StatusCorruption(pstatus, "_DictionaryLength: section size"); goto Exit; } // Is the name null-terminated? pvName = (void*)pent->sz; if( CP_WINUNICODE == _CodePage && S_OK != StringCchLengthW( (WCHAR*)pvName, pent->cch, NULL )) //L'\0' != ((WCHAR*)pvName)[ pent->cch - 1 ] ) { StatusCorruption(pstatus, "_DictionaryLength: section size"); goto Exit; } if( CP_WINUNICODE != _CodePage && S_OK != StringCchLengthA( (CHAR*)pvName, pent->cch, NULL )) //'\0' != ((CHAR*)pvName)[ pent->cch - 1 ] ) { StatusCorruption(pstatus, "_DictionaryLength: section size"); goto Exit; } cbDict += _DictionaryEntryLength( pent ); } // ---- // Exit // ---- Exit: return(cbDict); } //+-------------------------------------------------------------------------- // Member: CPropertySetStream::_IsLocalizationSettable // // Synopsis: Determine if this property set may be localized // (i.e., that the codepage & locale ID may be set). // // Arguments: [pstatus] -- pointer to NTSTATUS code // // Returns: TRUE if settable. //+-------------------------------------------------------------------------- BOOLEAN CPropertySetStream::_IsLocalizationSettable( OUT NTSTATUS *pstatus ) { BOOLEAN fSettable = FALSE; PROPERTYSECTIONHEADER const *psh; PROPERTYIDOFFSET *ppo, *ppoBase = NULL, *ppoMax = NULL; *pstatus = STATUS_SUCCESS; // Get the section header psh = _LoadPropertyOffsetPointers(&ppoBase, &ppoMax, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; // If the section is empty, then we're done. if( NULL == psh || 0 == psh->cProperties ) { fSettable = TRUE; goto Exit; } // Walk through the properties in the set for (ppo = ppoBase; ppo < ppoMax; ppo++) { // Is this the dictionary? if( PID_DICTIONARY == ppo->propid ) { // The dictionary is OK if it's empty. DICTIONARY const *pdy; pdy = reinterpret_cast ( _MapOffsetToAddress(ppo->dwOffset) ); if( 0 != pdy->cEntries ) goto Exit; // fSettable == FALSE } // Or, is this an existing codepage or LCID property // (which are fine to overwrite)? else if( PID_CODEPAGE != ppo->propid && PID_LOCALE != ppo->propid ) { // No, so we're done. goto Exit; // fSettable == FALSE } } fSettable = TRUE; Exit: return( fSettable ); } //+-------------------------------------------------------------------------- // 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: Minimum format version (wFormat) required for this name. // // 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'). // //+-------------------------------------------------------------------------- WORD CPropertySetStream::_PropertyNameLength( IN VOID const *pvName, OUT ULONG *pcbName) const { ULONG cchsz; if (_CodePage == CP_WINUNICODE) { cchsz = (ULONG)Prop_wcslen((WCHAR const *) pvName) + 1; *pcbName = cchsz * sizeof(WCHAR); } else { *pcbName = cchsz = (ULONG)strlen((char const *) pvName) + 1; } return( cchsz > CCH_MAXPROPNAMESZ ? PROPSET_WFORMAT_LONG_NAMES : PROPSET_WFORMAT_ORIGINAL ); } // CPropertySetStream::_PropertyNameLength //+-------------------------------------------------------------------------- // 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: 2nd name is wrong endian in a big endian compile // [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, OUT NTSTATUS *pstatus ) const { #ifdef BIGENDIAN // If fSameByteOrder, we need to swap pvName2. #error BigEndian support in this routine needs to be implemented #endif int nCompare = 0; *pstatus = STATUS_SUCCESS; PROPASSERT( NULL != pvName1 && NULL != pvName2 ); PROPASSERT( _SupportsLongNames() || CCH_MAXPROPNAMESZ >= CB2CCh(cbName) ); // Now compare the strings if (_CodePage == CP_WINUNICODE) { // Compare Unicode strings PROPASSERT( IsUnicodeString(reinterpret_cast(pvName1),cbName) && IsUnicodeString(reinterpret_cast(pvName2),cbName) ); if( _IsCaseSensitive() ) { nCompare = CompareStringW( _Locale, 0, reinterpret_cast(pvName1), -1, reinterpret_cast(pvName2), -1 ); } else { nCompare = CompareStringW( _Locale, NORM_IGNORECASE, reinterpret_cast(pvName1), -1, reinterpret_cast(pvName2), -1 ); } } else { // Compare Ansi strings PROPASSERT( IsAnsiString(reinterpret_cast(pvName1), cbName) && IsAnsiString(reinterpret_cast(pvName2), cbName) ); if( _IsCaseSensitive() ) { nCompare = CompareStringA( _Locale, 0, reinterpret_cast(pvName1), -1, reinterpret_cast(pvName2), -1 ); } else { nCompare = CompareStringA( _Locale, NORM_IGNORECASE, reinterpret_cast(pvName1), -1, reinterpret_cast(pvName2), -1 ); } } if( CSTR_EQUAL == nCompare ) return TRUE; else if( 0 == nCompare ) { StatusError( pstatus, "Failed CompareString", HRESULT_FROM_WIN32(GetLastError()) ); } return FALSE; } // CPropertySetStream::_ComparePropertyNames() //+--------------------------------------------------------------------------- // Function: CPropertySetStream::DuplicatePropertyName // // Synopsis: Duplicate an OLECHAR property name string // // Arguments: [poszName] -- input string // [pstatus] -- pointer to NTSTATUS code // // Returns: pointer to new string //--------------------------------------------------------------------------- OLECHAR * CPropertySetStream::DuplicatePropertyName( IN OLECHAR const *poszName, OUT NTSTATUS *pstatus) const { OLECHAR *poc = NULL; *pstatus = STATUS_SUCCESS; ULONG cbName = (1 + ocslen(poszName)) * sizeof(OLECHAR); if (cbName != 0) { 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; IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::QueryPropid" ); propTraceParameters(( "poszName=%s", poszName )); // ---------- // 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; } 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, including terminator)? _PropertyNameLength(pvName, &cbname); // Ensure that this isn't an empty name. if( CP_WINUNICODE == _CodePage && sizeof(WCHAR) == cbname || CP_WINUNICODE != _CodePage && sizeof(CHAR) == cbname) { // Empty names are invalid StatusInvalidParameter(pstatus, "QueryPropid: name length"); goto Exit; } // Get a pointer to the raw dictionary (this implicitly validates // the 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 )) { // If the byte-length matches what we're looking for, // and the names compare successfully, then we're done. if ( CCh2CB(PropByteSwap( pent->cch )) == cbname && _ComparePropertyNames(pvName, pent->sz, FALSE, // pvName, pent->sz could be dif Endians cbname, pstatus) ) { propid = PropByteSwap( pent->propid ); break; } else if( !NT_SUCCESS(*pstatus) ) { // There was an error during the property name comparison goto Exit; } } // 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::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 any of the properties exist. //+-------------------------------------------------------------------------- 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); // IFDBG( HRESULT &hr = *pstatus ); // propITrace( "CPropertySetStream::QueryPropertyNames" ); // propTraceParameters(( "cprop=%d, apid=%p, aposz=%p", cprop, apid, aposz )); // 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. #if DBG for (iprop = 0; iprop < cprop; iprop++) { PROPASSERT(aposz[iprop] == NULL); } #endif // Get a pointer to the beginning of the dictionary // (this implicitely validates 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(pent->propid) == 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( (CHAR*)pent->sz, (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*) pent->sz; // 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*) pent->sz ) CoTaskMemFree( 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 *) pent->sz, 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 (pent->propid == 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; IFDBG( HRESULT &hr = *pstatus ); propITrace( "CPropertySetStream::SetPropertyNames" ); propTraceParameters(( "cprop=%d, apid=%p, aposz=%p", cprop, apid, aposz )); // ---------- // Initialize // ---------- *pstatus = STATUS_SUCCESS; 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; } if (aposz != NULL) { for (iprop = 0; iprop < cprop; iprop++) { PROPASSERT( IsOLECHARString( aposz[iprop], MAXULONG )); } } // 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 **) CoTaskMemAlloc( sizeof(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; // Silently ignore PID_ILLEGAL if( PID_ILLEGAL == apid[iprop] ) continue; // Convert from aposz to appvNames 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 **) CoTaskMemAlloc( sizeof(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; // Silently ignore PID_ILLEGAL if( PID_ILLEGAL == apid[iprop] ) continue; // Convert from aposz to appvNames 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; WORD wFormatRequired; // Silently ignore PID_ILLEGAL if( PID_ILLEGAL == apid[iprop] ) continue; // => for (iprop = 0; iprop < cprop; iprop++) // Validate the caller-provided length. wFormatRequired = _PropertyNameLength(appvNames[iprop], &cbname); if( CP_WINUNICODE == _CodePage && sizeof(WCHAR) == cbname || CP_WINUNICODE != _CodePage && sizeof(CHAR) == cbname) { // Empty names are not supported StatusInvalidParameter(pstatus, "SetPropertyNames: name length"); goto Exit; } // Update the format if necessary (long property names were not // supported in version 0). _pph->wFormat = max( _pph->wFormat, wFormatRequired ); // See if this propid or name appears later in the array. If so, // we'll ignore it and wait for the final instance. 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, pstatus)) { fDupName = TRUE; break; } else if( !NT_SUCCESS(*pstatus) ) { // There was an error in _ComparePropertyNames goto Exit; } } // 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) { propDbg(( DEB_ITRACE, _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_DICTIONARY_ENTRY + cbname; if( _CodePage == CP_WINUNICODE ) { cbAdd = DwordAlign( cbAdd ); } } } } PROPASSERT( _CodePage == CP_WINUNICODE ? IsDwordAligned( cbAdd ) : TRUE ); // --------------------------------------------- // Get the dictionary, creating it if necessary. // --------------------------------------------- _SetModified( pstatus ); if( !NT_SUCCESS(*pstatus) ) { propDbg(( DEB_ERROR, "SetPropertyNames: Couldn't SetModified (%08x)\n", *pstatus )); goto Exit; } // Make two passes at getting the dictionary. If it doesn't exist, create one and // try again. for (i = 0; i <= 2; i++) { PROPERTY_INFORMATION pinfo; PROPVARIANT var; // We should never require more than two passes if( 2 <= i ) { StatusError( pstatus, "SetPropertyNames: infinite loop", STATUS_INTERNAL_DB_CORRUPTION ); goto Exit; } // Get the dictionary (this implicitely validates it). pdy = (DICTIONARY *) _LoadProperty(PID_DICTIONARY, &cbDictOld, pstatus); if( !NT_SUCCESS(*pstatus) ) goto Exit; // If we have the dictionary, move on (out of this loop) if (pdy != NULL) { break; } // There's no dictionary, we need to create one. PROPASSERT(i == 0); // Of course, if we're being asked to delete the dictionary or to do // nothing, then we're done (no need to create an empty one). if (cprop == 0 || appvNames == NULL) { goto Exit; } // If we get this far, we need to creat a new dictionary. propDbg(( DEB_ITRACE, "Creating empty dictionary\n")); PROPASSERT(CB_SERIALIZEDPROPERTYVALUE == CB_DICTIONARY); // Since the dictionary is actually implemented as a special property, // we just have to call SetValue (there's some special-case code in SetValue, // though, for this case). pinfo.cbprop = CB_SERIALIZEDPROPERTYVALUE; pinfo.pid = PID_DICTIONARY; var.vt = VT_DICTIONARY; SetValue(1, NULL, &var, &pinfo, NULL, 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 <= 2; i++) // We now have the dictionary in pdy // ---------------------------------------------------------------- // 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 )) { propDbg(( DEB_ITRACE, _CodePage == CP_WINUNICODE? "Dictionary Entry @%lx: propid=%lx L'%ws'\n" : "Dictionary Entry @%lx: propid=%lx '%s'\n", pent, PropByteSwap( pent->propid ), pent->sz )); // For this dictionary entry, walk the caller's // 'apid' and 'appvNames' arrays, looking for a match. for (iprop = 0; iprop < cprop; iprop++) { // Silently ignore PID_ILLEGAL if( PID_ILLEGAL == apid[iprop] ) continue; // 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( pent->propid )) { // 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( pent->cch )) || !_ComparePropertyNames( appvNames[iprop], pent->sz, FALSE, // appvNames & pent->sz may be dif endians. cbname, pstatus ) ) { // Check to see if there was an error from _ComparePropertyNames if( !NT_SUCCESS(*pstatus) ) goto Exit; continue; } // Show that at least one property delete is by name. // (This is for an optimization later). fDeleteByName = TRUE; } // if (!fDeleteAll ... // If we reach this point, we're going to delete this entry // in the dictionary (either by propid, by name, or because // of fDeleteAll). So update cDel & cbDel. propDbg(( DEB_ITRACE, "Deleting Entry (%s) @%lx: propid=%lx\n", fDeleteAll? "DeleteAll" : apid[iprop] == PropByteSwap(pent->propid) ? "replace by propid" : "replace by name", pent, PropByteSwap( pent->propid ))); 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]; ... // At this point we've figured out what to add and delete. // cDel is the number of deletions and cbDel is the amount // of space occupied by those names. Same for cAdd and cbAdd. 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 = cbstm - (_oSection + oDictionary + cbDictOldD); // -------------------------------------------------------- // Before we change anything, grow the stream if necessary. // -------------------------------------------------------- if (cbChangeD > 0) { propDbg(( DEB_ITRACE, "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; // Get a pointer to the new dictionary location 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; // Loop through the dictionary entries for (i = 0; i < PropByteSwap(pdy->cEntries); i++) { // Search to see if this entry is in the caller's apid array. for (iprop = 0; iprop < cprop; iprop++) { // Did we find an entry in the caller's array that is // this dictionary entry? if( apid[iprop] == PropByteSwap(pent->propid) ) { // Yes. Break out so that we can overwrite // the old value. break; } // The propid didn't match this entry, but see if we're deleting // any of the properties by name. This fDeleteByName indicates // that at least one property delete is by name. This exists // to optimize the case wher all of the properties are specified // by ID, in that case we can skip all these semi-expensive operations. if (fDeleteByName) { ULONG cbname; _PropertyNameLength(appvNames[iprop], &cbname); if (cbname == CCh2CB( PropByteSwap( pent->cch )) && _ComparePropertyNames( appvNames[iprop], pent->sz, FALSE, // appvNames & pent->sz may be dif endians cbname, pstatus) ) { break; // found an entry to be removed. } else if( !NT_SUCCESS(*pstatus) ) { // There was an error in _ComparePropertyNames goto Exit; } } } // for (iprop = 0; iprop < cprop; iprop++) // When we get to this point, we've figured out whether or not // this entry is being overwritten (if it is, iprop is less than // cprop). // Get info about this entry. cb = _DictionaryEntryLength( pent ); pent = _NextDictionaryEntry( pent ); // Are we overwriting this entry? if (iprop == cprop) { // No - keep this entry. Keep track of how // big it is, so that we can copy it forward to cover // up any earlier wholes left by names we're going to // overwrite/delete. cbCopy += cb; } else { // Yes - this entry is being overwritten // Do we have to-keep names to copy forward? if (cbCopy != 0) { // Yes. If we've never copied anything yet, // src == dst, and we don't actually need to move // anything. if (pvSrc != pvDst) { // Move a group of to-keep names forward, overwrting // some to-delete names. PropMoveMemory( "SetPropertyNames:Compact", _GetSectionHeader(), pvDst, pvSrc, cbCopy); } // Move pvDst past the names we're keeping. pvDst = Add2Ptr(pvDst, cbCopy); cbCopy = 0; } // Move pvSrc to this property - this is the next potential // place from which we'll copy. 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) // Update the dictionary entry count according to what we // just deleted. Note that pent is left pointing to the first // available entry. pdy->cEntries = PropByteSwap( PropByteSwap(pdy->cEntries) - cDel ); // ------------------------------------ // Append new and modified entries now. // ------------------------------------ // New/modified entries get added at the end of the dictionary, in the whole left // by the deleted/overwritten names above and by the SetSize earlier. 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 // (but only bother with this if we know there was at // least one dup in apid). i = cprop; if (fDupPropid) { for (i = iprop + 1; i < cprop; i++) { if (apid[i] == apid[iprop]) { break; } } } // If this is the last instance of the propid in the // apid array, see if this name appears later in the appvNames array // (but only bother with this check if we know there was at // least one dup name). 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, pstatus)) { break; } else if( !NT_SUCCESS(*pstatus) ) // There was an error in _ComparePropertyNames goto Exit; } } // Silently ignore PID_ILLEGAL if( PID_ILLEGAL == apid[iprop] ) continue; // If this propid & name 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); pent->propid = PropByteSwap( apid[iprop] ); pent->cch = PropByteSwap( CB2CCh( cbname )); // Copy the name into the dictionary. RtlCopyMemory(pent->sz, appvNames[iprop], cbname); // If this is a Unicode property set, we need to correct // the byte-order. if( CP_WINUNICODE == _CodePage ) { PBSBuffer( pent->sz, cbname, sizeof(WCHAR) ); } // Zero-out the pad bytes. RtlZeroMemory( Add2Ptr(pent->sz, cbname), DwordRemain((ULONG) (ULONG_PTR) pent->sz + cbname)); // Move pent to the next available entry. 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; // Did the dictionary shrink overall? if (cbChangeD < 0) { // Yes, it shrunk. // Move everything after the dictionary forward by cbChangeD bytes. PropMoveMemory( "SetPropertyNames:TailUp", _GetSectionHeader(), Add2Ptr(pdy, cbDictOldD + cbChangeD), Add2Ptr(pdy, cbDictOldD), cbTail); } // If there's a section after this one in the property set, // update the section offsets in the property set header. 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. if ( cbChangeD < 0 ) { propDbg(( DEB_ITRACE, "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] ); } CoTaskMemFree( appvNames ); } 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 *poszName = NULL; *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( STATUS_NOT_SUPPORTED == *pstatus ) // We're working with an up-level property set *pstatus = STATUS_SUCCESS; else 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; fExists = QueryPropertyNames( 1, &propid, &poszName, pstatus ); if( !NT_SUCCESS(*pstatus) ) goto Exit; if( !fExists ) { break; } else { _pma->Free( poszName ); poszName = NULL; } } 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 = reinterpret_cast ( CoTaskMemAlloc( sizeof(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; // Size of a prop according to PROPID/Offset table ULONG cbpropraw;// Size of prop based on knowledge of the type ULONG cbprop; // cbpropraw + padding for alignment SERIALIZEDPROPERTYVALUE const *pprop = NULL; 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 = PropertyLengthNoEH(pprop, cbdiff, 0, pstatus); if( STATUS_NOT_SUPPORTED == *pstatus ) { // We're working with an up-level property set. // Assume it's OK. cbprop = cbdiff; *pstatus = STATUS_SUCCESS; } else 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; // As long as we're looking at the properties, let's check for // property types that require a minimum format version (wFormat). if( PID_DICTIONARY == ppo->propid ) ; // pprop->dwType isn't actually a type for the dictionary property else if( PROPSET_WFORMAT_EXPANDED_VTS > GetFormatVersion() && ( !IsOriginalPropVariantType( static_cast(PropByteSwap( pprop->dwType ))) || (VT_BYREF & PropByteSwap(pprop->dwType)) ) ) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateProperties(bad value type: propid/vt=%x/%x @%x/%x cb=%x/%x/%x ppsstm=%x)\n", ppo->propid, pprop->dwType, ppo->dwOffset, pprop, cbpropraw, cbprop, cbdiff, this)); StatusCorruption(pstatus, "_ValidateProperties: bad property type"); goto Exit; } // 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: CoTaskMemFree( 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(((ENTRYVALIDATE const *) pev1)->pent->propid - ((ENTRYVALIDATE const *) pev2)->pent->propid); } #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; NTSTATUS Status = STATUS_SUCCESS; pent1 = ((ENTRYVALIDATE const *) pev1)->pent; pent2 = ((ENTRYVALIDATE const *) pev2)->pent; rc = PropByteSwap(pent1->cch) - PropByteSwap(pent2->cch); if (rc == 0) { rc = !((ENTRYVALIDATE const *) pev1)->ppsstm->_ComparePropertyNames( pent1->sz, pent2->sz, TRUE, // Both names have the same byte-order ( (ENTRYVALIDATE const *) pev1 )->ppsstm->CCh2CB(PropByteSwap( pent1->cch )), &Status ); } 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 = reinterpret_cast ( CoTaskMemAlloc( sizeof(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 the cb is greater than the max allowed in original // property sets (after allowing for padding and per-entry // overhead), then check the wFormat field in the header. if( CB2CCh(cb) > CCH_MAXPROPNAME + CB_DICTIONARY_ENTRY + sizeof(DWORD) ) { if( PROPSET_WFORMAT_LONG_NAMES > GetFormatVersion() ) { StatusCorruption(pstatus, "ValidateDictionary: entry size too big for wFormat"); goto Exit; } } if (Add2ConstPtr(pent, cb) > pvDictEnd) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateDictionary(bad entry size for propid=%x)\n", PropByteSwap( pev->pent->propid ))); StatusCorruption(pstatus, "ValidateDictionary: entry size"); goto Exit; } pev->pent = pent; pev->ppsstm = this; #ifdef LITTLEENDIAN if (_CodePage == CP_WINUNICODE) { PROPASSERT(IsUnicodeString((WCHAR const *) pent->sz, CCh2CB(PropByteSwap( pent->cch )))); } else { PROPASSERT(IsAnsiString((char const *) pent->sz, CCh2CB( PropByteSwap( pent->cch )))); } #endif 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(pev->pent->propid) || pev[1].pent->propid == pev->pent->propid) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateDictionary(bad propid=%x)\n", PropByteSwap( pev->pent->propid ))); 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 (pev->pent->cch == 0 || ( pev->pent->cch == pev[1].pent->cch && _ComparePropertyNames( pev->pent->sz, pev[1].pent->sz, TRUE, // Names are the same byte-order CCh2CB(PropByteSwap(pev->pent->cch)), pstatus) ) ) { DebugTrace(0, DEBTRACE_ERROR, ( "_ValidateDictionary(bad name for propid=%x)\n", PropByteSwap( pev->pent->propid ))); StatusCorruption(pstatus, "_ValidateDictionary: bad or dup name"); goto Exit; } else if( !NT_SUCCESS(*pstatus) ) // There was an error in _ComparePropertyNames goto Exit; } // for (pev = aev; pev < pevMax; pev++) } // if (pdy != NULL && pdy->cEntries != 0) // ---- // Exit // ---- Exit: CoTaskMemFree( aev ); } #endif // DBGPROP //+-------------------------------------------------------------------------- // Member: CPropertySetStream::Validate // // Synopsis: validate entire property stream // // Arguments: [pstatus] -- pointer to NTSTATUS code // // Returns: None //+-------------------------------------------------------------------------- #if DBGPROP extern "C" 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 //+-------------------------------------------------------------------------- // Function: CopyPropertyValue // // Synopsis: copy a property value into a supplied buffer // // Arguments: [pprop] -- property value (possibly NULL) // [cb] -- property length // [ppropDst] -- output buffer for property value // [pcb] -- length of buffer (in); actual length (out) // // Returns: None //--------------------------------------------------------------------------- #ifdef WINNT VOID CopyPropertyValue( IN OPTIONAL SERIALIZEDPROPERTYVALUE const *pprop, IN ULONG cb, OUT SERIALIZEDPROPERTYVALUE *ppropDst, OUT ULONG *pcb) { #if DBG==1 NTSTATUS Status; #endif if (pprop == NULL) { static SERIALIZEDPROPERTYVALUE prop = { VT_EMPTY, }; pprop = ∝ cb = CB_SERIALIZEDPROPERTYVALUE; } PROPASSERT(cb == PropertyLengthNoEH(pprop, cb, 0, &Status) && NT_SUCCESS(Status) ); RtlCopyMemory(ppropDst, pprop, min(cb, *pcb)); *pcb = cb; } #endif // WINNT