|
|
// DirectorySpecification.cpp: implementation of the CDirectorySpecification class.
//
// Copyright (c) 1997-2001 Microsoft Corporation, All Rights Reserved
//
//////////////////////////////////////////////////////////////////////
#include "precomp.h"
#include "DirectorySpecification.h"
#include "ExtendString.h"
#include "ExtendQuery.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CDirectorySpecification::CDirectorySpecification(CRequestObject *pObj, IWbemServices *pNamespace, IWbemContext *pCtx):CGenericClass(pObj, pNamespace, pCtx) {
}
CDirectorySpecification::~CDirectorySpecification() {
}
HRESULT CDirectorySpecification::CreateObject(IWbemObjectSink *pHandler, ACTIONTYPE atAction) { HRESULT hr = WBEM_S_NO_ERROR;
MSIHANDLE hView = NULL; MSIHANDLE hRecord = NULL; MSIHANDLE hDView = NULL; MSIHANDLE hDRecord = NULL;
int i = -1; WCHAR * wcBuf = NULL; WCHAR * wcDir = NULL; WCHAR * wcPath = NULL; WCHAR * wcProductCode = NULL; WCHAR * wcCompID = NULL; WCHAR * wcDirectory = NULL; WCHAR * wcTestCode = NULL;
DWORD dwBufSize; bool bMatch = false; UINT uiStatus; bool bGotID = false; bool bDoneFirst = false;
try { if ( ( wcBuf = new WCHAR [ BUFF_SIZE ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
if ( ( wcDir = new WCHAR [ BUFF_SIZE ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
if ( ( wcPath = new WCHAR [ BUFF_SIZE * 4 ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
if ( ( wcProductCode = new WCHAR [ 39 ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
if ( ( wcCompID = new WCHAR [ 39 ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
if ( ( wcDirectory = new WCHAR [ BUFF_SIZE ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
if ( ( wcTestCode = new WCHAR [ 39 ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
DWORD dwBufSizeDir = BUFF_SIZE; DWORD dwBufSizeBuf = BUFF_SIZE;
//These will change from class to class
bool bCheck, bValidated; INSTALLSTATE piInstalled; int iState;
SetSinglePropertyPath(L"CheckID");
//improve getobject performance by optimizing the query
if(atAction != ACTIONTYPE_ENUM) { // we are doing GetObject so we need to be reinitialized
hr = WBEM_E_NOT_FOUND;
BSTR bstrCompare;
int iPos = -1; bstrCompare = SysAllocString ( L"CheckID" );
if ( bstrCompare ) { if(FindIn(m_pRequest->m_Property, bstrCompare, &iPos)) { if ( ::SysStringLen ( m_pRequest->m_Value[iPos] ) < BUFF_SIZE ) { //Get the action we're looking for
wcscpy(wcBuf, m_pRequest->m_Value[iPos]);
// safe operation if wcslen ( wcBuf ) > 38
if ( wcslen ( wcBuf ) > 38 ) { wcscpy(wcTestCode, &(wcBuf[(wcslen(wcBuf) - 38)])); } else { // we are not good to go, they have sent us longer string
SysFreeString ( bstrCompare ); throw hr; }
// safe because lenght has been tested already in condition
RemoveFinalGUID(m_pRequest->m_Value[iPos], wcDirectory);
// safe because lenght is going to be at least 39
//we have a componentized directory... do a little more work
if ( (wcDirectory[wcslen(wcDirectory) - 1] == L'}') && (wcDirectory[wcslen(wcDirectory) - 38] == L'{') ) { RemoveFinalGUID(wcDirectory, wcDirectory); }
bGotID = true; } else { // we are not good to go, they have sent us longer string
SysFreeString ( bstrCompare ); throw hr; }
}
SysFreeString ( bstrCompare ); } else { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); } }
CStringExt wcID;
Query wcQuery; wcQuery.Append ( 1, L"select distinct `Directory`, `DefaultDir` from Directory" );
//optimize for GetObject
if ( bGotID ) { wcQuery.Append ( 3, L" where `Directory`=\'", wcDirectory, L"\'" ); }
QueryExt wcQuery1 ( L"select distinct `ComponentId`, `Component` from Component where `Directory_`=\'" );
LPWSTR Buffer = NULL; LPWSTR dynBuffer = NULL;
DWORD dwDynBuffer = 0L;
while(!bMatch && m_pRequest->Package(++i) && (hr != WBEM_E_CALL_CANCELLED)) { // safe operation:
// Package ( i ) returns NULL ( tested above ) or valid WCHAR [39]
wcscpy(wcProductCode, m_pRequest->Package(i));
if((atAction == ACTIONTYPE_ENUM) || (bGotID && (_wcsicmp(wcTestCode, wcProductCode) == 0))){
//Open our database
try { if ( GetView ( &hView, wcProductCode, wcQuery, L"Directory", FALSE, FALSE ) ) { uiStatus = g_fpMsiViewFetch(hView, &hRecord);
while(!bMatch && (uiStatus != ERROR_NO_MORE_ITEMS) && (hr != WBEM_E_CALL_CANCELLED)){ CheckMSI(uiStatus); bDoneFirst = false;
UINT uiStatusTemp = ERROR_SUCCESS;
//create different instances for each software element
dwBufSize = dwBufSizeDir;; if ( ( uiStatusTemp = g_fpMsiRecordGetStringW(hRecord, 1, wcDir, &dwBufSize) )== ERROR_MORE_DATA ) { delete [] wcDir; wcDir = NULL;
if ( ( wcDir = new WCHAR [ dwBufSize + 1 ] ) != NULL ) { CheckMSI ( g_fpMsiRecordGetStringW(hRecord, 1, wcDir, &dwBufSize) ); dwBufSizeDir = dwBufSize; } else { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); } } else { if ( uiStatusTemp != ERROR_SUCCESS ) { CheckMSI ( uiStatusTemp ); } }
// make query on fly
wcQuery1.Append ( 2, wcDir, L"\'" );
if ( ( ( uiStatus = g_fpMsiDatabaseOpenViewW ( msidata.GetDatabase (), wcQuery1, &hDView ) ) == ERROR_SUCCESS )
|| !bDoneFirst ) { if((g_fpMsiViewExecute(hDView, 0) == ERROR_SUCCESS) || !bDoneFirst){
try{
uiStatus = g_fpMsiViewFetch(hDView, &hDRecord);
while(!bMatch && (!bDoneFirst || (uiStatus == ERROR_SUCCESS)) && (hr != WBEM_E_CALL_CANCELLED)){ bValidated = false;
if(uiStatus == ERROR_SUCCESS){
dwBufSize = 39; CheckMSI(g_fpMsiRecordGetStringW(hDRecord, 1, wcCompID, &dwBufSize)); bValidated = ValidateComponentID(wcCompID, wcProductCode); }
if(((uiStatus != ERROR_SUCCESS) && !bDoneFirst) || (bValidated && (uiStatus != ERROR_NO_MORE_ITEMS))){
if(FAILED(hr = SpawnAnInstance(&m_pObj))) throw hr;
//----------------------------------------------------
PutProperty(m_pObj, pDirectory, wcDir);
wcID.Copy ( wcDir );
DWORD dwCompID = 0L; LPWSTR wszTemp = NULL;
if(uiStatus == ERROR_SUCCESS) { dwCompID = wcslen ( wcCompID );
try { if ( ( wszTemp = new WCHAR [ dwCompID + 1 ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); } } catch ( ... ) { if ( wszTemp ) { delete [] wszTemp; wszTemp = NULL; }
throw; }
wcscpy(wszTemp, wcCompID); PutProperty(m_pObj, pSoftwareElementID, wcCompID); }
if ( dwCompID ) { wcID.Append ( 1, wszTemp );
if ( wszTemp ) { delete [] wszTemp; wszTemp = NULL; } }
wcID.Append ( 1, wcProductCode ); PutKeyProperty(m_pObj, pCheckID, wcID, &bCheck, m_pRequest);
//====================================================
dwBufSize = BUFF_SIZE * 4;
BOOL bContinue = TRUE; DWORD dwContinue= 2;
DWORD dwStatus = ERROR_SUCCESS;
do { if ( ( dwStatus = CreateDirectoryPath ( msidata.GetProduct (), msidata.GetDatabase (), wcDir, wcPath, &dwBufSize ) ) == ERROR_MORE_DATA ) { delete [] wcPath; wcPath = NULL;
if ( ( wcPath = new WCHAR [ dwBufSize + 1 ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); } } else { bContinue = FALSE; } } while ( bContinue && dwContinue-- );
if ( dwStatus == ERROR_SUCCESS ) { PutProperty(m_pObj, pDirectoryPath, wcPath); }
dwBufSize = dwBufSizeBuf; PutPropertySpecial ( hRecord, 2, dwBufSize, wcBuf, dwDynBuffer, dynBuffer, FALSE, 3, pDefaultDir, pCaption, pDescription );
if(uiStatus == ERROR_SUCCESS){ dwBufSize = dwBufSizeBuf;
if ( ( uiStatusTemp = g_fpMsiRecordGetStringW(hDRecord, 2, wcBuf, &dwBufSize) ) == ERROR_MORE_DATA ) { delete [] wcBuf; wcBuf = NULL;
if ( ( wcBuf = new WCHAR [ dwBufSize + 1 ] ) != NULL ) { CheckMSI(g_fpMsiRecordGetStringW(hDRecord, 2, wcBuf, &dwBufSize)); dwBufSizeBuf = dwBufSize; } else { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); } } else { if ( uiStatusTemp != ERROR_SUCCESS ) { CheckMSI ( uiStatusTemp ); } }
PutProperty(m_pObj, pName, wcBuf); dwBufSize = dwBufSizeBuf; piInstalled = g_fpMsiGetComponentPathW(wcProductCode, wcCompID, wcBuf, &dwBufSize); SoftwareElementState(piInstalled, &iState); PutProperty(m_pObj, pSoftwareElementState, iState);
PutProperty(m_pObj, pTargetOperatingSystem, GetOS());
dwBufSize = dwBufSizeBuf; CheckMSI(g_fpMsiGetProductPropertyW(msidata.GetProduct(), L"ProductVersion", wcBuf, &dwBufSize)); PutProperty(m_pObj, pVersion, wcBuf); }
//----------------------------------------------------
if(bCheck) bMatch = true;
if((atAction != ACTIONTYPE_GET) || bMatch){
hr = pHandler->Indicate(1, &m_pObj); }
m_pObj->Release(); m_pObj = NULL;
if(!bDoneFirst) bDoneFirst = true; }
g_fpMsiCloseHandle(hDRecord);
uiStatus = g_fpMsiViewFetch(hDView, &hDRecord); }
}catch(...){
g_fpMsiCloseHandle(hDRecord); g_fpMsiViewClose(hDView); g_fpMsiCloseHandle(hDView); throw; }
g_fpMsiCloseHandle(hDRecord); g_fpMsiViewClose(hDView); g_fpMsiCloseHandle(hDView); } }
g_fpMsiCloseHandle(hRecord);
uiStatus = g_fpMsiViewFetch(hView, &hRecord); } } } catch(...) { if ( dynBuffer ) { delete [] dynBuffer; dynBuffer = NULL; }
g_fpMsiCloseHandle(hRecord); g_fpMsiViewClose(hView); g_fpMsiCloseHandle(hView);
msidata.CloseDatabase (); msidata.CloseProduct ();
if(m_pObj) { m_pObj->Release(); m_pObj = NULL; }
throw; }
g_fpMsiCloseHandle(hRecord); g_fpMsiViewClose(hView); g_fpMsiCloseHandle(hView);
msidata.CloseDatabase (); msidata.CloseProduct (); } }
if ( dynBuffer ) { delete [] dynBuffer; dynBuffer = NULL; } } catch ( ... ) { if (wcBuf) { delete [] wcBuf; wcBuf = NULL; }
if (wcDir) { delete [] wcDir; wcDir = NULL; }
if (wcPath) { delete [] wcPath; wcPath = NULL; }
if (wcProductCode) { delete [] wcProductCode; wcProductCode = NULL; }
if (wcCompID) { delete [] wcCompID; wcCompID = NULL; }
if (wcDirectory) { delete [] wcDirectory; wcDirectory = NULL; }
if (wcTestCode) { delete [] wcTestCode; wcTestCode = NULL; }
throw; }
if (wcBuf) { delete [] wcBuf; wcBuf = NULL; }
if (wcDir) { delete [] wcDir; wcDir = NULL; }
if (wcPath) { delete [] wcPath; wcPath = NULL; }
if (wcProductCode) { delete [] wcProductCode; wcProductCode = NULL; }
if (wcCompID) { delete [] wcCompID; wcCompID = NULL; }
if (wcDirectory) { delete [] wcDirectory; wcDirectory = NULL; }
if (wcTestCode) { delete [] wcTestCode; wcTestCode = NULL; }
return hr; }
DWORD CDirectorySpecification::CreateDirectoryPath ( MSIHANDLE hProduct, MSIHANDLE hDatabase, WCHAR *wcDir, WCHAR *wcPath, DWORD *dwPath ) { DWORD dwResult = static_cast < DWORD > ( E_INVALIDARG );
MSIHANDLE hView = NULL; MSIHANDLE hRecord = NULL;
LPWSTR wcQuery = NULL; LPWSTR wcBuf = NULL;
if ( wcDir ) { DWORD dwQuery = 0L; LPWSTR wszQuery= L"select distinct `Directory_Parent`, `DefaultDir` from Directory where `Directory`=\'";
dwQuery = lstrlenW ( wszQuery ) + lstrlenW ( wcDir ) + 1 + 1;
try { if ( ( wcQuery = new WCHAR [ dwQuery ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
wcscpy(wcQuery, wszQuery); wcscat(wcQuery, wcDir); wcscat(wcQuery, L"\'"); } catch ( ... ) { if ( wcQuery ) { delete [] wcQuery; wcQuery = NULL; }
throw; }
try { if ( ( wcBuf = new WCHAR [ BUFF_SIZE ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); } } catch ( ... ) { if ( wcQuery ) { delete [] wcQuery; wcQuery = NULL; }
if ( wcBuf ) { delete [] wcBuf; wcBuf = NULL; }
throw; }
DWORD dwPathSize = 0; dwPathSize = * dwPath;
DWORD dwUsed = 1; // last null
DWORD dwBufSize = BUFF_SIZE;
//Do all this to open a view on the directory we want
if ( ( dwResult = g_fpMsiDatabaseOpenViewW ( hDatabase, wcQuery, &hView ) ) == ERROR_SUCCESS ) { delete [] wcQuery; wcQuery = NULL;
if(g_fpMsiViewExecute(hView, 0) == ERROR_SUCCESS) { if ( ( dwResult = g_fpMsiViewFetch ( hView, &hRecord ) ) == ERROR_SUCCESS ) { BOOL bContinue = TRUE; DWORD dwContinue= 2;
dwBufSize = BUFF_SIZE;
do { if ( ( dwResult = g_fpMsiRecordGetStringW ( hRecord, 1, wcBuf, &dwBufSize ) ) == ERROR_MORE_DATA ) { delete [] wcBuf; wcBuf = NULL;
try { if ( ( wcBuf = new WCHAR [ dwBufSize + 1 ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); } } catch ( ... ) { if ( wcBuf ) { delete [] wcBuf; wcBuf = NULL; }
throw; }
dwBufSize++; } else { if ( dwContinue == 2 ) { dwBufSize = BUFF_SIZE; }
bContinue = FALSE; } } while ( bContinue && dwContinue-- );
if( dwResult == ERROR_SUCCESS ) { //For TARGETDIR
if(wcscmp(L"TARGETDIR", wcBuf) == 0) { bContinue = TRUE; dwContinue= 2;
DWORD dwBufSizeOld = dwBufSize;
do { if ( ( dwResult = g_fpMsiGetProductPropertyW ( hProduct, L"TARGETDIR", wcBuf, &dwBufSize ) ) == ERROR_MORE_DATA ) { delete [] wcBuf; wcBuf = NULL;
try { if ( ( wcBuf = new WCHAR [ dwBufSize + 1 ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
dwBufSize ++; } catch ( ... ) { if ( wcBuf ) { delete [] wcBuf; wcBuf = NULL; }
throw; } } else { if ( dwContinue == 2 ) { dwBufSize = dwBufSizeOld; }
bContinue = FALSE; } } while ( bContinue && dwContinue-- );
if ( dwResult == ERROR_SUCCESS ) { dwUsed = dwUsed + lstrlenW ( wcBuf );
if ( dwUsed > dwPathSize ) { ( *dwPath ) = ( *dwPath ) + ( dwUsed - dwPathSize ); dwResult = ERROR_MORE_DATA; } else { wcscpy(wcPath, wcBuf); } } } //For WindowsFolder
else if(wcscmp(L"WindowsFolder", wcBuf) == 0) { DWORD dwSize = 0;
bContinue = TRUE; dwContinue= 2;
do { dwSize = GetEnvironmentVariableW ( L"WINDIR", wcBuf, dwBufSize );
if ( dwSize == 0 ) { dwResult = static_cast < DWORD > ( E_FAIL ); bContinue = FALSE; } else if ( dwSize > dwBufSize ) { delete [] wcBuf; wcBuf = NULL;
try { if ( ( wcBuf = new WCHAR [ dwSize + 1 ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
dwBufSize = dwSize + 1; } catch ( ... ) { if ( wcBuf ) { delete [] wcBuf; wcBuf = NULL; }
throw; } } else { bContinue = FALSE; dwResult = ERROR_SUCCESS; } } while ( bContinue && dwContinue-- );
if ( dwResult == ERROR_SUCCESS ) { dwUsed = dwUsed + lstrlenW ( wcBuf );
if ( dwUsed > dwPathSize ) { ( *dwPath ) = ( *dwPath ) + ( dwUsed - dwPathSize ); dwResult = ERROR_MORE_DATA; } else { wcscpy(wcPath, wcBuf); } } } //For DesktopFolder
else if(wcscmp(L"DesktopFolder", wcBuf) == 0) { WCHAR wcVar[15];
if(AreWeOnNT()) { wcscpy(wcVar, L"USERPROFILE"); } else { wcscpy(wcVar, L"WINDIR"); }
DWORD dwSize = 0;
bContinue = TRUE; dwContinue= 2;
do { dwSize = GetEnvironmentVariableW ( wcVar, wcBuf, dwBufSize );
if ( dwSize == 0 ) { dwResult = static_cast < DWORD > ( E_FAIL ); bContinue = FALSE; } else if ( dwSize > dwBufSize ) { delete [] wcBuf; wcBuf = NULL;
try { if ( ( wcBuf = new WCHAR [ dwSize + 1 ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
dwBufSize = dwSize + 1; } catch ( ... ) { if ( wcBuf ) { delete [] wcBuf; wcBuf = NULL; }
throw; } } else { bContinue = FALSE; dwResult = ERROR_SUCCESS; } } while ( bContinue && dwContinue-- );
if ( dwResult == ERROR_SUCCESS ) { dwUsed = dwUsed + lstrlenW ( wcBuf ) + lstrlenW ( L"\\Desktop" );
if ( dwUsed > dwPathSize ) { ( *dwPath ) = ( *dwPath ) + ( dwUsed - dwPathSize ); dwResult = ERROR_MORE_DATA; } else { wcscpy(wcPath, wcBuf); wcscat(wcPath, L"\\Desktop"); } } } //For same parent/directory
else if(wcscmp(wcDir, wcBuf) == 0) { dwResult = ERROR_SUCCESS; } //Continue recursion
else { dwResult = CreateDirectoryPath ( hProduct, hDatabase, wcBuf, wcPath, dwPath );
if ( dwResult == ERROR_MORE_DATA ) { dwUsed = dwUsed + ( * dwPath ); } } }
if ( dwResult == ERROR_SUCCESS || dwResult == ERROR_MORE_DATA ) { bContinue = TRUE; dwContinue= 2;
DWORD dwResultHelp = ERROR_SUCCESS;
do { if ( ( dwResultHelp = g_fpMsiRecordGetStringW ( hRecord, 2, wcBuf, &dwBufSize ) ) == ERROR_MORE_DATA ) { delete [] wcBuf; wcBuf = NULL;
try { if ( ( wcBuf = new WCHAR [ dwBufSize ] ) == NULL ) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); } } catch ( ... ) { if ( wcBuf ) { delete [] wcBuf; wcBuf = NULL; }
throw; } } else { bContinue = FALSE; } } while ( bContinue && dwContinue-- );
if ( dwResultHelp == ERROR_MORE_DATA ) { dwResult = static_cast < DWORD > ( E_FAIL ); }
if ( dwResult == ERROR_SUCCESS || dwResult == ERROR_MORE_DATA ) { LPWSTR wcBufHelp = NULL; try { wcBufHelp = ParseDefDir ( wcBuf ); } catch ( ... ) { delete [] wcBuf; wcBuf = NULL;
throw; }
dwUsed = dwUsed + lstrlenW ( wcBufHelp );
if ( dwUsed > dwPathSize ) { ( *dwPath ) = ( *dwPath ) + ( dwUsed - dwPathSize ); dwResult = ERROR_MORE_DATA; } else { wcscat(wcPath, wcBufHelp); } } }
g_fpMsiCloseHandle(hRecord);
} else if ( dwResult == E_OUTOFMEMORY ) { g_fpMsiViewClose(hView); g_fpMsiCloseHandle(hView);
delete [] wcBuf; wcBuf = NULL;
throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); } }
g_fpMsiViewClose(hView); g_fpMsiCloseHandle(hView);
delete [] wcBuf; wcBuf = NULL; } else { delete [] wcBuf; wcBuf = NULL;
delete [] wcQuery; wcQuery = NULL;
if(dwResult == E_OUTOFMEMORY) { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); } } }
if ( dwResult == ERROR_MORE_DATA ) { wcPath [ 0 ] = 0; }
return dwResult; }
WCHAR * CDirectorySpecification::ParseDefDir(WCHAR *wcDefaultDir) { WCHAR * wcTmp; WCHAR * wcBuf = NULL; if ( ( wcBuf = (WCHAR *)malloc( ( wcslen ( wcDefaultDir ) + 1 + 1 ) * sizeof(WCHAR)) ) != NULL ) { wcscpy(wcBuf, L"\\"); wcscat(wcBuf, wcDefaultDir);
for(wcTmp = wcBuf; *wcTmp; wcTmp++) if(*wcTmp == L':') *wcTmp = NULL; for(wcTmp = wcBuf; *wcTmp; wcTmp++) if(*wcTmp == L'.') wcscpy(wcBuf, L"");
wcscpy(wcDefaultDir, wcBuf);
free((void *)wcBuf); } else { throw CHeap_Exception(CHeap_Exception::E_ALLOCATION_ERROR); }
return wcDefaultDir; }
|