You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1140 lines
33 KiB
1140 lines
33 KiB
/*
|
|
****************************************************************************
|
|
| Copyright (C) 2002 Microsoft Corporation
|
|
|
|
|
| Component / Subcomponent
|
|
| IIS 6.0 / IIS Migration Wizard
|
|
|
|
|
| Based on:
|
|
| http://iis6/Specs/IIS%20Migration6.0_Final.doc
|
|
|
|
|
| Abstract:
|
|
| IIS Metabase access classes implementation
|
|
|
|
|
| Author:
|
|
| ivelinj
|
|
|
|
|
| Revision History:
|
|
| V1.00 March 2002
|
|
|
|
|
****************************************************************************
|
|
*/
|
|
#include "StdAfx.h"
|
|
#include "IISHelpers.h"
|
|
#include "Utils.h"
|
|
#include "resource.h"
|
|
|
|
|
|
// CIISSite implementation
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
CIISSite::CIISSite( ULONG nSiteID, bool bReadOnly /*= true */ )
|
|
{
|
|
IMSAdminBasePtr spIABO;
|
|
METADATA_HANDLE hSite = NULL;
|
|
|
|
// Create the ABO
|
|
IF_FAILED_HR_THROW( spIABO.CreateInstance( CLSID_MSAdminBase ),
|
|
CObjectException( IDS_E_CREATEINSTANCE, L"CLSID_MSAdminBase" ) );
|
|
|
|
// Open the site
|
|
WCHAR wszSitePath[ MAX_PATH + 1 ];
|
|
::swprintf( wszSitePath, L"LM/W3SVC/%u", nSiteID );
|
|
|
|
HRESULT hr = spIABO->OpenKey( METADATA_MASTER_ROOT_HANDLE,
|
|
wszSitePath,
|
|
bReadOnly ? METADATA_PERMISSION_READ : ( METADATA_PERMISSION_WRITE | METADATA_PERMISSION_READ ),
|
|
KeyAccessTimeout,
|
|
&hSite );
|
|
|
|
if ( HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND ) == hr )
|
|
{
|
|
throw CBaseException( IDS_E_WEBNOTFOUND, ERROR_SUCCESS );
|
|
}
|
|
else if ( FAILED( hr ) )
|
|
{
|
|
throw CBaseException( IDS_E_SITEOPEN, hr );
|
|
}
|
|
|
|
m_spIABO.Attach( spIABO.Detach() );
|
|
m_hSiteHandle = hSite;
|
|
}
|
|
|
|
|
|
|
|
CIISSite::~CIISSite()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Creates a new empty web site entry in the metabase
|
|
dwHint is the first ID to try.
|
|
Returns the new SiteID
|
|
*/
|
|
DWORD CIISSite::CreateNew( DWORD dwHint /*=1*/ )
|
|
{
|
|
// Creating new sites is available only on Server platforms
|
|
_ASSERT( CTools::GetOSVer() & 1 );
|
|
|
|
DWORD dwCurrentID = dwHint;
|
|
WCHAR wszSitePath[ 64 ]; // Should be large enough for max DWORD value
|
|
|
|
IMSAdminBasePtr spIABO;
|
|
METADATA_HANDLE hData = NULL;
|
|
|
|
// Create the ABO
|
|
IF_FAILED_HR_THROW( spIABO.CreateInstance( CLSID_MSAdminBase ),
|
|
CObjectException( IDS_E_CREATEINSTANCE, L"CLSID_MSAdminBase" ) );
|
|
|
|
// Open the W3SVC path
|
|
IF_FAILED_HR_THROW( spIABO->OpenKey( METADATA_MASTER_ROOT_HANDLE,
|
|
L"LM/W3SVC",
|
|
METADATA_PERMISSION_WRITE,
|
|
KeyAccessTimeout,
|
|
&hData ),
|
|
CObjectException( IDS_E_METABASE, L"LM/W3SVC" ) );
|
|
|
|
HRESULT hr = E_FAIL;
|
|
|
|
do
|
|
{
|
|
::swprintf( wszSitePath, L"%u", dwCurrentID++ );
|
|
|
|
// Try to create this site id
|
|
hr = spIABO->AddKey( hData, wszSitePath );
|
|
}while( HRESULT_FROM_WIN32( ERROR_ALREADY_EXISTS ) == hr );
|
|
|
|
VERIFY( SUCCEEDED( spIABO->CloseKey( hData ) ) );
|
|
|
|
IF_FAILED_HR_THROW( hr, CObjectException( IDS_E_MD_ADDKEY, L"[NewSiteID]" ) );
|
|
|
|
return dwCurrentID - 1;
|
|
}
|
|
|
|
|
|
|
|
void CIISSite::DeleteSite( DWORD dwSiteID )
|
|
{
|
|
// Do not try to delete the default web
|
|
_ASSERT( dwSiteID > 1 );
|
|
|
|
WCHAR wszSitePath[ 64 ];
|
|
|
|
IMSAdminBasePtr spIABO;
|
|
METADATA_HANDLE hData = NULL;
|
|
|
|
// Create the ABO
|
|
IF_FAILED_HR_THROW( spIABO.CreateInstance( CLSID_MSAdminBase ),
|
|
CObjectException( IDS_E_CREATEINSTANCE, L"CLSID_MSAdminBase" ) );
|
|
|
|
// Open the W3SVC path
|
|
IF_FAILED_HR_THROW( spIABO->OpenKey( METADATA_MASTER_ROOT_HANDLE,
|
|
L"LM/W3SVC",
|
|
METADATA_PERMISSION_WRITE,
|
|
KeyAccessTimeout,
|
|
&hData ),
|
|
CObjectException( IDS_E_METABASE, L"LM/W3SVC" ) );
|
|
|
|
::swprintf( wszSitePath, L"%u", dwSiteID );
|
|
|
|
HRESULT hr = spIABO->DeleteKey( hData, wszSitePath );
|
|
|
|
VERIFY( SUCCEEDED( spIABO->CloseKey( hData ) ) );
|
|
|
|
if ( FAILED( hr ) && ( hr != HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND ) ) )
|
|
{
|
|
throw CBaseException( IDS_E_METABASE_IO, hr );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
This methods writes the metabase data to the XML doc. spRoot is the XML element under which
|
|
the data should be written. hEncryptKey is used to encrypt all secure properties.
|
|
The crypt key may be null in which case the properties are exported in clear text ( that happens when
|
|
the whole package will be encrypted )
|
|
*/
|
|
void CIISSite::ExportConfig( const IXMLDOMDocumentPtr& spXMLDoc,
|
|
const IXMLDOMElementPtr& spRoot,
|
|
HCRYPTKEY hEncryptKey )const
|
|
{
|
|
_ASSERT( spRoot != NULL );
|
|
_ASSERT( m_hSiteHandle != NULL ); // Site must be opened first
|
|
_ASSERT( m_spIABO != NULL );
|
|
|
|
IXMLDOMElementPtr spMDRoot;
|
|
IXMLDOMElementPtr spInheritRoot;
|
|
|
|
// We will put the metadata under the XML tag <Metadata>
|
|
// Under this node we will have <IISConfigObject> for each metakey under the site's root key
|
|
// We will have one tag <MD_Inherit> bellow <Metadata>, where all the inheritable properties will be written
|
|
// ( using the <Custom> tag )
|
|
|
|
// Create our root node for metadata and the root node for inheritable data
|
|
spMDRoot = CXMLTools::CreateSubNode( spXMLDoc, spRoot, L"Metadata" );
|
|
spInheritRoot = CXMLTools::CreateSubNode( spXMLDoc, spMDRoot, L"MD_Inherit" );
|
|
|
|
// We need a buffer for reading the metadata for each key
|
|
// As there might be a lot of keys instead of allocating the buffer for each key
|
|
// we will allocate it here and the ExportKey method will use it
|
|
// ( ExportKey may modify the buffer. i.e. nake it bigger )
|
|
DWORD dwDefaultBufferSize = 4 * 1024;
|
|
TByteAutoPtr spBuffer( new BYTE[ dwDefaultBufferSize ] );
|
|
|
|
// Export the current site config. This will export any subnodes as well
|
|
// NOTE: this will not export inherited data
|
|
ExportKey( spXMLDoc, spMDRoot, hEncryptKey, L"", /*r*/spBuffer, /*r*/dwDefaultBufferSize );
|
|
|
|
// Export ONLY the inheritable data
|
|
// This will export all the data that the Site metadata key ( LM/W3SVC/### ) inherits from it's
|
|
// parent ( LM/W3SVC )
|
|
ExportInheritData( spXMLDoc, spInheritRoot, hEncryptKey, /*r*/spBuffer, /*r*/dwDefaultBufferSize );
|
|
|
|
// Remove any non-exportable data
|
|
RemoveLocalMetadata( spRoot );
|
|
}
|
|
|
|
|
|
/*
|
|
Exports the site's SSL certificate into the provided smart pointer
|
|
Caller must check first if the site has SSL certificate with HaveCertificate method
|
|
*/
|
|
void CIISSite::ExportCert( const IXMLDOMDocumentPtr& spDoc,
|
|
const IXMLDOMElementPtr& spRoot,
|
|
LPCWSTR wszPassword )const
|
|
{
|
|
_ASSERT( m_spIABO != NULL );
|
|
_ASSERT( m_hSiteHandle != NULL );
|
|
_ASSERT( spRoot != NULL );
|
|
_ASSERT( spDoc != NULL );
|
|
_ASSERT( wszPassword != NULL );
|
|
_ASSERT( HaveCertificate() );
|
|
|
|
TCertContextHandle shCert( GetCert() );
|
|
|
|
// Create a certificate store in memory. We will put the certificate in this mem store
|
|
// and then export this mem store which will export the certificate as well
|
|
TCertStoreHandle shMemStore( ::CertOpenStore( CERT_STORE_PROV_MEMORY,
|
|
0,
|
|
0,
|
|
CERT_STORE_READONLY_FLAG,
|
|
NULL ) );
|
|
IF_FAILED_BOOL_THROW( shMemStore.IsValid(),
|
|
CBaseException( IDS_E_OPEN_CERT_STORE ) );
|
|
// Add our certificate to the mem store
|
|
IF_FAILED_BOOL_THROW( ::CertAddCertificateContextToStore( shMemStore.get(),
|
|
shCert.get(),
|
|
CERT_STORE_ADD_REPLACE_EXISTING,
|
|
NULL ),
|
|
CBaseException( IDS_E_ADD_CERT_STORE ) );
|
|
|
|
// Add the certificate chain to the store
|
|
// ( the certificate chain is all the certificates starting from this certificate up to a trusted,
|
|
// self-signed certificate. We need to do this, as the root certificate may not be trusted on the target
|
|
// machine and this will render the SSL certificate invalid ( not trusted )
|
|
ChainCertificate( shCert.get(), shMemStore.get() );
|
|
|
|
CRYPT_DATA_BLOB Data = { 0 };
|
|
|
|
// Get the size of the encrypted data
|
|
::PFXExportCertStoreEx( shMemStore.get(),
|
|
&Data,
|
|
wszPassword,
|
|
NULL,
|
|
EXPORT_PRIVATE_KEYS );
|
|
_ASSERT( Data.cbData > 0 );
|
|
|
|
// Alloc the space end get the data
|
|
TByteAutoPtr spData = TByteAutoPtr( new BYTE[ Data.cbData ] );
|
|
Data.pbData = spData.get();
|
|
|
|
IF_FAILED_BOOL_THROW( ::PFXExportCertStoreEx( shMemStore.get(),
|
|
&Data,
|
|
wszPassword,
|
|
NULL,
|
|
EXPORT_PRIVATE_KEYS | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY ),
|
|
CBaseException( IDS_E_EXPORT_CERT ) );
|
|
|
|
// Create the XML element to hold the certificate data
|
|
CXMLTools::AddTextNode( spDoc,
|
|
spRoot,
|
|
L"Certificate",
|
|
Convert::ToString( spData.get(), Data.cbData ).c_str() );
|
|
}
|
|
|
|
|
|
|
|
void CIISSite::ImportConfig( const IXMLDOMNodePtr& spSite, HCRYPTKEY hDecryptKey, bool bImportInherited )const
|
|
{
|
|
_ASSERT( spSite != NULL );
|
|
|
|
IXMLDOMNodeListPtr spPaths;
|
|
IXMLDOMNodePtr spConfig;
|
|
IXMLDOMNodeListPtr spValueList;
|
|
IXMLDOMNodePtr spValue;
|
|
|
|
IF_FAILED_HR_THROW( spSite->selectNodes( _bstr_t( L"Metadata/IISConfigObject" ), &spPaths ),
|
|
CBaseException( IDS_E_XML_PARSE ) );
|
|
|
|
while( S_OK == spPaths->nextNode( &spConfig ) )
|
|
{
|
|
std::wstring strLocation = CXMLTools::GetAttrib( spConfig, L"Location" );
|
|
|
|
if ( !strLocation.empty() )
|
|
{
|
|
AddKey( strLocation.c_str() );
|
|
}
|
|
|
|
// Import every <Custom> tag in this Config object
|
|
|
|
IF_FAILED_HR_THROW( spConfig->selectNodes( _bstr_t( L"Custom" ), &spValueList ),
|
|
CBaseException( IDS_E_XML_PARSE ) );
|
|
while( S_OK == spValueList->nextNode( &spValue ) )
|
|
{
|
|
ImportMetaValue( spValue, strLocation.c_str(), hDecryptKey );
|
|
}
|
|
}
|
|
|
|
// Import the inherited values
|
|
if ( bImportInherited )
|
|
{
|
|
IF_FAILED_HR_THROW( spSite->selectNodes( _bstr_t( L"Metadata/MD_Inherit" ), &spValueList ),
|
|
CBaseException( IDS_E_XML_PARSE ) );
|
|
|
|
while( S_OK == spValueList->nextNode( &spValue ) )
|
|
{
|
|
ImportMetaValue( spValue, NULL, hDecryptKey );
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Copies currrent site name into wszName
|
|
wszName must be at least METADATA_MAX_NAME_LEN + 1
|
|
*/
|
|
const std::wstring CIISSite::GetDisplayName()const
|
|
{
|
|
_ASSERT( m_hSiteHandle != NULL );
|
|
_ASSERT( m_spIABO != NULL );
|
|
|
|
DWORD dwUnsued = 0;
|
|
WCHAR wszBuffer[ METADATA_MAX_NAME_LEN + 1 ];
|
|
METADATA_RECORD md = { 0 };
|
|
|
|
md.dwMDAttributes = 0;
|
|
md.dwMDIdentifier = MD_SERVER_COMMENT;
|
|
md.dwMDDataType = ALL_METADATA;
|
|
md.dwMDDataLen = ( METADATA_MAX_NAME_LEN + 1 ) * sizeof( WCHAR );
|
|
md.pbMDData = reinterpret_cast<BYTE*>( wszBuffer );
|
|
|
|
IF_FAILED_HR_THROW( m_spIABO->GetData( m_hSiteHandle, NULL, &md, &dwUnsued ),
|
|
CBaseException( IDS_E_METABASE_IO ) );
|
|
|
|
return std::wstring( wszBuffer );
|
|
}
|
|
|
|
|
|
|
|
const std::wstring CIISSite::GetAnonUser()const
|
|
{
|
|
_ASSERT( m_spIABO != NULL );
|
|
_ASSERT( m_hSiteHandle != NULL );
|
|
|
|
DWORD dwSize = 0;
|
|
METADATA_RECORD md = { 0 };
|
|
|
|
md.dwMDAttributes = METADATA_INHERIT;
|
|
md.dwMDIdentifier = MD_ANONYMOUS_USER_NAME;
|
|
md.dwMDDataType = ALL_METADATA;
|
|
md.dwMDDataLen = 0;
|
|
md.pbMDData = NULL;
|
|
|
|
VERIFY( FAILED( m_spIABO->GetData( m_hSiteHandle, NULL, &md, &dwSize ) ) );
|
|
_ASSERT( dwSize > 0 );
|
|
|
|
std::auto_ptr<WCHAR> spBuffer( new WCHAR[ dwSize / sizeof( WCHAR ) ] );
|
|
md.dwMDDataLen = dwSize;
|
|
md.pbMDData = reinterpret_cast<BYTE*>( spBuffer.get() );
|
|
|
|
IF_FAILED_HR_THROW( m_spIABO->GetData( m_hSiteHandle, NULL, &md, &dwSize ),
|
|
CBaseException( IDS_E_METABASE_IO ) );
|
|
|
|
return std::wstring( spBuffer.get() );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Checks if the current IIS Site has a SSL certificate
|
|
*/
|
|
bool CIISSite::HaveCertificate()const
|
|
{
|
|
_ASSERT( m_spIABO != NULL );
|
|
_ASSERT( m_hSiteHandle != NULL );
|
|
|
|
// Get the cert hash value from the metabase
|
|
METADATA_RECORD md = { 0 };
|
|
DWORD dwHashSize = 0;
|
|
|
|
md.dwMDDataType = ALL_METADATA;
|
|
md.dwMDIdentifier = MD_SSL_CERT_HASH;
|
|
|
|
// Do not get the data - just check if it is there
|
|
HRESULT hr = m_spIABO->GetData( m_hSiteHandle,
|
|
NULL,
|
|
&md,
|
|
&dwHashSize );
|
|
|
|
// If the data is not found - no SSL cert
|
|
if ( FAILED( hr ) )
|
|
{
|
|
if ( MD_ERROR_DATA_NOT_FOUND == hr )
|
|
{
|
|
return false;
|
|
}
|
|
else if( hr != HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) )
|
|
{
|
|
// Unexpected error
|
|
throw CBaseException( IDS_E_METABASE_IO, hr );
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// We can't get here - the above call should always fail
|
|
_ASSERT( false );
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
void CIISSite::BackupMetabase( LPCWSTR wszLocation /*=NULL */ )
|
|
{
|
|
IMSAdminBasePtr spIABO;
|
|
IF_FAILED_HR_THROW( spIABO.CreateInstance( CLSID_MSAdminBase ),
|
|
CObjectException( IDS_E_CREATEINSTANCE, L"CLSID_MSAdminBase" ) );
|
|
|
|
IF_FAILED_HR_THROW( spIABO->Backup( wszLocation,
|
|
MD_BACKUP_NEXT_VERSION,
|
|
MD_BACKUP_SAVE_FIRST ),
|
|
CBaseException( IDS_E_MDBACKUP ) );
|
|
}
|
|
|
|
|
|
|
|
void CIISSite::SetKeyData( LPCWSTR wszPath, DWORD dwID, DWORD dwUserType, LPCWSTR wszData )const
|
|
{
|
|
_ASSERT( wszData != NULL );
|
|
_ASSERT( m_hSiteHandle != NULL );
|
|
_ASSERT( m_spIABO != NULL );
|
|
|
|
METADATA_RECORD md = { 0 };
|
|
|
|
md.dwMDIdentifier = dwID;
|
|
md.dwMDDataType = STRING_METADATA;
|
|
md.dwMDUserType = dwUserType;
|
|
md.dwMDDataLen = static_cast<DWORD>( ( ::wcslen( wszData ) + 1 ) * sizeof( WCHAR ) );
|
|
md.pbMDData = ( BYTE* )( wszData );
|
|
|
|
IF_FAILED_HR_THROW( m_spIABO->SetData( m_hSiteHandle, wszPath, &md ),
|
|
CBaseException( IDS_E_METABASE_IO ) );
|
|
}
|
|
|
|
|
|
|
|
const std::wstring CIISSite::GetDefaultAnonUser()
|
|
{
|
|
DWORD dwSize = 0;
|
|
METADATA_RECORD md = { 0 };
|
|
|
|
md.dwMDAttributes = METADATA_INHERIT;
|
|
md.dwMDIdentifier = MD_ANONYMOUS_USER_NAME;
|
|
md.dwMDDataType = ALL_METADATA;
|
|
md.dwMDDataLen = 0;
|
|
md.pbMDData = NULL;
|
|
|
|
IMSAdminBasePtr spIABO;
|
|
METADATA_HANDLE hKey = NULL;
|
|
|
|
// Create the ABO
|
|
IF_FAILED_HR_THROW( spIABO.CreateInstance( CLSID_MSAdminBase ),
|
|
CObjectException( IDS_E_CREATEINSTANCE, L"CLSID_MSAdminBase" ) );
|
|
IF_FAILED_HR_THROW( spIABO->OpenKey( METADATA_MASTER_ROOT_HANDLE,
|
|
_bstr_t( L"LM/W3SVC" ),
|
|
METADATA_PERMISSION_READ,
|
|
KeyAccessTimeout,
|
|
&hKey ),
|
|
CBaseException( IDS_E_METABASE_IO ) );
|
|
|
|
VERIFY( FAILED( spIABO->GetData( hKey, NULL, &md, &dwSize ) ) );
|
|
_ASSERT( dwSize > 0 );
|
|
|
|
std::auto_ptr<WCHAR> spBuffer( new WCHAR[ dwSize / sizeof( WCHAR ) ] );
|
|
md.dwMDDataLen = dwSize;
|
|
md.pbMDData = reinterpret_cast<BYTE*>( spBuffer.get() );
|
|
|
|
HRESULT hr = spIABO->GetData( hKey, NULL, &md, &dwSize );
|
|
|
|
VERIFY( SUCCEEDED( spIABO->CloseKey( hKey ) ) );
|
|
|
|
IF_FAILED_HR_THROW( hr, CBaseException( IDS_E_METABASE_IO ) );
|
|
|
|
return std::wstring( spBuffer.get() );
|
|
}
|
|
|
|
|
|
|
|
// Implementation
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
void CIISSite::ExportKey( const IXMLDOMDocumentPtr& spDoc,
|
|
const IXMLDOMElementPtr& spRoot,
|
|
HCRYPTKEY hCryptKey,
|
|
LPCWSTR wszNodePath,
|
|
TByteAutoPtr& rspBuffer,
|
|
DWORD& rdwBufferSize )const
|
|
{
|
|
_ASSERT( m_hSiteHandle != NULL );
|
|
_ASSERT( m_spIABO != NULL );
|
|
_ASSERT( spDoc != NULL );
|
|
_ASSERT( wszNodePath != NULL );
|
|
_ASSERT( rdwBufferSize > 0 );
|
|
_ASSERT( rspBuffer.get() != NULL );
|
|
|
|
// Insert this key entry into the XML file
|
|
IXMLDOMElementPtr spCurrentKey = CXMLTools::CreateSubNode( spDoc, spRoot, L"IISConfigObject" );
|
|
CXMLTools::SetAttrib( spCurrentKey, L"Location", wszNodePath );
|
|
|
|
WCHAR wszSubKey[ METADATA_MAX_NAME_LEN + 1 ];
|
|
|
|
// Write this node data to the XML
|
|
ExportKeyData( spDoc, spCurrentKey, hCryptKey, wszNodePath, /*r*/rspBuffer, /*r*/rdwBufferSize );
|
|
|
|
// Enum any subkeys
|
|
// They are not nested in the XML
|
|
DWORD iKey = 0;
|
|
while( true )
|
|
{
|
|
// Get the next keyname
|
|
HRESULT hr = m_spIABO->EnumKeys( m_hSiteHandle, wszNodePath, wszSubKey, iKey++ );
|
|
|
|
if ( FAILED( hr ) )
|
|
{
|
|
if ( HRESULT_FROM_WIN32( ERROR_NO_MORE_ITEMS ) == hr ) break;
|
|
else throw CBaseException( IDS_E_METABASE_IO, hr );
|
|
}
|
|
|
|
WCHAR wszSubkeyPath[ METADATA_MAX_NAME_LEN + 1 ];
|
|
::swprintf( wszSubkeyPath, L"%s/%s", wszNodePath, wszSubKey );
|
|
|
|
// Export subkeys of the current subkey
|
|
ExportKey( spDoc, spRoot, hCryptKey, wszSubkeyPath, /*r*/rspBuffer, /*r*/rdwBufferSize );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CIISSite::ExportInheritData( const IXMLDOMDocumentPtr& spDoc,
|
|
const IXMLDOMElementPtr& spInheritRoot,
|
|
HCRYPTKEY hEncryptKey,
|
|
TByteAutoPtr& rspBuffer,
|
|
DWORD& rdwBufferSize )const
|
|
{
|
|
_ASSERT( spDoc != NULL );
|
|
_ASSERT( spInheritRoot != NULL );
|
|
_ASSERT( m_spIABO != NULL );
|
|
_ASSERT( rspBuffer.get() != NULL );
|
|
_ASSERT( rdwBufferSize > 0 );
|
|
|
|
DWORD dwEntries = 0;
|
|
DWORD dwUnused = 0; // DataSetNumber - we don't care
|
|
|
|
while( true )
|
|
{
|
|
DWORD dwRequiredSize = rdwBufferSize;
|
|
|
|
HRESULT hr = m_spIABO->GetAllData( m_hSiteHandle,
|
|
NULL,
|
|
METADATA_SECURE | METADATA_INSERT_PATH | METADATA_INHERIT | METADATA_ISINHERITED,
|
|
ALL_METADATA,
|
|
ALL_METADATA,
|
|
&dwEntries,
|
|
&dwUnused,
|
|
rdwBufferSize,
|
|
rspBuffer.get(),
|
|
&dwRequiredSize );
|
|
// Increase the buffer if we need to
|
|
if ( HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) == hr )
|
|
{
|
|
_ASSERT( dwRequiredSize > rdwBufferSize );
|
|
rdwBufferSize = dwRequiredSize;
|
|
rspBuffer = TByteAutoPtr( new BYTE[ rdwBufferSize ] );
|
|
continue;
|
|
}
|
|
else if ( FAILED( hr ) )
|
|
{
|
|
throw CBaseException( IDS_E_METABASE_IO, hr );
|
|
}
|
|
else
|
|
{
|
|
// SUCCEEDED
|
|
break;
|
|
}
|
|
};
|
|
|
|
METADATA_GETALL_RECORD* aRecords = reinterpret_cast<METADATA_GETALL_RECORD*>( rspBuffer.get() );
|
|
|
|
for ( DWORD i = 0; i < dwEntries; ++i )
|
|
{
|
|
// Store the record in the XML file only if this metadata is inherited from the parent
|
|
if ( aRecords[ i ].dwMDAttributes & METADATA_ISINHERITED )
|
|
{
|
|
// Remove the inheritance attribs - we don't need them and ExportMetaRecord will
|
|
// not recognize them
|
|
aRecords[ i ].dwMDAttributes &= ~METADATA_ISINHERITED;
|
|
|
|
// Set the inherit attrib as this is an inheritable data
|
|
// ( event it is not now - it should be. At import time this data will be applied
|
|
// to the WebSite root node as not-inherited but inheritable data
|
|
aRecords[ i ].dwMDAttributes |= METADATA_INHERIT;
|
|
|
|
ExportMetaRecord( spDoc,
|
|
spInheritRoot,
|
|
hEncryptKey,
|
|
aRecords[ i ],
|
|
rspBuffer.get() + aRecords[ i ].dwMDDataOffset );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CIISSite::ExportKeyData( const IXMLDOMDocumentPtr& spDoc,
|
|
const IXMLDOMElementPtr& spKey,
|
|
HCRYPTKEY hCryptKey,
|
|
LPCWSTR wszNodePath,
|
|
TByteAutoPtr& rspBuffer,
|
|
DWORD& rdwBufferSize )const
|
|
{
|
|
_ASSERT( wszNodePath != NULL );
|
|
_ASSERT( spDoc != NULL );
|
|
_ASSERT( spKey != NULL );
|
|
_ASSERT( m_spIABO != NULL );
|
|
_ASSERT( rspBuffer.get() != NULL );
|
|
_ASSERT( rdwBufferSize > 0 );
|
|
|
|
DWORD dwEntries = 0;
|
|
DWORD dwUnused = 0; // DataSetNumber - we don't care
|
|
|
|
do
|
|
{
|
|
DWORD dwRequiredSize = rdwBufferSize;
|
|
|
|
HRESULT hr = m_spIABO->GetAllData( m_hSiteHandle,
|
|
wszNodePath,
|
|
METADATA_SECURE | METADATA_INSERT_PATH,
|
|
ALL_METADATA,
|
|
ALL_METADATA,
|
|
&dwEntries,
|
|
&dwUnused,
|
|
rdwBufferSize,
|
|
rspBuffer.get(),
|
|
&dwRequiredSize );
|
|
// Increase the buffer if we need to
|
|
if ( HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) == hr )
|
|
{
|
|
_ASSERT( dwRequiredSize > rdwBufferSize );
|
|
rdwBufferSize = dwRequiredSize;
|
|
rspBuffer = TByteAutoPtr( new BYTE[ rdwBufferSize ] );
|
|
continue;
|
|
}
|
|
else if ( FAILED( hr ) )
|
|
{
|
|
throw CBaseException( IDS_E_METABASE_IO, hr );
|
|
}
|
|
}while( false );
|
|
|
|
METADATA_GETALL_RECORD* aRecords = reinterpret_cast<METADATA_GETALL_RECORD*>( rspBuffer.get() );
|
|
|
|
for ( DWORD i = 0; i < dwEntries; ++i )
|
|
{
|
|
// Store the record in the XML file
|
|
ExportMetaRecord( spDoc, spKey, hCryptKey, aRecords[ i ], rspBuffer.get() + aRecords[ i ].dwMDDataOffset );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CIISSite::DecryptData( HCRYPTKEY hDecryptKey, LPWSTR wszData )const
|
|
{
|
|
_ASSERT( hDecryptKey != NULL );
|
|
_ASSERT( wszData != NULL );
|
|
|
|
TByteAutoPtr spData;
|
|
DWORD dwSize = 0;
|
|
|
|
Convert::ToBLOB( wszData, /*r*/spData, /*r*/dwSize );
|
|
|
|
// Decrypt data "in-place"
|
|
// We are using stream cypher, so the length of the encrypted and decrypted string is the same
|
|
IF_FAILED_BOOL_THROW( ::CryptDecrypt( hDecryptKey,
|
|
NULL,
|
|
TRUE,
|
|
0,
|
|
spData.get(),
|
|
&dwSize ),
|
|
CBaseException( IDS_E_CRYPT_ENCRYPT ) );
|
|
|
|
_ASSERT( ::wcslen( wszData ) * sizeof( WCHAR ) == dwSize );
|
|
|
|
::CopyMemory( wszData, spData.get(), dwSize );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Returns the Site's SSL certificate context hanlde
|
|
*/
|
|
const TCertContextHandle CIISSite::GetCert()const
|
|
{
|
|
_ASSERT( m_spIABO != NULL );
|
|
_ASSERT( m_hSiteHandle != NULL );
|
|
_ASSERT( HaveCertificate() );
|
|
|
|
// Get the cert hash value from the metabase
|
|
METADATA_RECORD md = { 0 };
|
|
DWORD dwHashSize = 0;
|
|
TByteAutoPtr spHash;
|
|
|
|
md.dwMDDataType = ALL_METADATA;
|
|
md.dwMDIdentifier = MD_SSL_CERT_HASH;
|
|
|
|
// Do not get the data - just check if it is there
|
|
HRESULT hr = m_spIABO->GetData( m_hSiteHandle,
|
|
NULL,
|
|
&md,
|
|
&dwHashSize );
|
|
|
|
// We should find the cert - HaveCertificate() is expected to be called prior this method
|
|
if ( FAILED( hr ) )
|
|
{
|
|
if( hr != HRESULT_FROM_WIN32( ERROR_INSUFFICIENT_BUFFER ) )
|
|
{
|
|
// Unexpected error
|
|
throw CBaseException( IDS_E_METABASE_IO, hr );
|
|
}
|
|
else
|
|
{
|
|
// Alloc space for the hash
|
|
_ASSERT( dwHashSize > 0 );
|
|
spHash = TByteAutoPtr( new BYTE[ dwHashSize ] );
|
|
md.dwMDDataLen = dwHashSize;
|
|
md.pbMDData = spHash.get();
|
|
}
|
|
}
|
|
|
|
IF_FAILED_HR_THROW( m_spIABO->GetData( m_hSiteHandle,
|
|
NULL,
|
|
&md,
|
|
&dwHashSize ),
|
|
CBaseException( IDS_E_METABASE_IO ) );
|
|
|
|
// Get the certificate from the store
|
|
// The store that keeps the certificates that have assosiated private keys
|
|
// is the system store named "MY"
|
|
TCertStoreHandle shStore( ::CertOpenStore( CERT_STORE_PROV_SYSTEM,
|
|
0, // Unused for the current store type
|
|
NULL, // Deafult crypt provider
|
|
CERT_SYSTEM_STORE_LOCAL_MACHINE,
|
|
L"MY" ) );
|
|
|
|
IF_FAILED_BOOL_THROW( shStore.IsValid(),
|
|
CBaseException( IDS_E_OPEN_CERT_STORE ) );
|
|
|
|
// Find the certificate in the store
|
|
CRYPT_HASH_BLOB Hash;
|
|
Hash.cbData = md.dwMDDataLen;
|
|
Hash.pbData = spHash.get();
|
|
TCertContextHandle shCert( ::CertFindCertificateInStore( shStore.get(),
|
|
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
|
0,
|
|
CERT_FIND_HASH,
|
|
&Hash,
|
|
NULL ) );
|
|
IF_FAILED_BOOL_THROW( shCert.IsValid(),
|
|
CBaseException( IDS_E_FIND_SSL_CERT ) );
|
|
|
|
return shCert;
|
|
}
|
|
|
|
|
|
/*
|
|
Adds hCert certificate chain to hStore
|
|
*/
|
|
void CIISSite::ChainCertificate( PCCERT_CONTEXT hCert, HCERTSTORE hStore )const
|
|
{
|
|
_ASSERT( hCert != NULL );
|
|
_ASSERT( hStore != NULL );
|
|
|
|
TCertChainHandle shCertChain;
|
|
|
|
// Use default chain parameters
|
|
CERT_CHAIN_PARA CertChainPara = { sizeof( CERT_CHAIN_PARA ) };
|
|
|
|
IF_FAILED_BOOL_THROW( ::CertGetCertificateChain( HCCE_LOCAL_MACHINE,
|
|
hCert,
|
|
NULL,
|
|
NULL,
|
|
&CertChainPara,
|
|
0,
|
|
NULL,
|
|
&shCertChain ),
|
|
CBaseException( IDS_E_CERT_CHAIN ) );
|
|
|
|
// There must be at least on simple chain
|
|
_ASSERT( shCertChain.get()->cChain != 0 );
|
|
|
|
unsigned i = 0;
|
|
while( i < shCertChain.get()->rgpChain[ 0 ]->cElement )
|
|
{
|
|
PCCERT_CONTEXT hCurrentCert = shCertChain.get()->rgpChain[ 0 ]->rgpElement[ i ]->pCertContext;
|
|
TCertContextHandle shTempCert;
|
|
|
|
// Add it to the store
|
|
IF_FAILED_BOOL_THROW( ::CertAddCertificateContextToStore( hStore,
|
|
hCurrentCert,
|
|
CERT_STORE_ADD_REPLACE_EXISTING,
|
|
&shTempCert ),
|
|
CBaseException( IDS_E_ADD_CERT_STORE ) );
|
|
|
|
// As this code is used for the SSL certificate ( the hCert )
|
|
// we don't need any root certificates' private keys
|
|
VERIFY( ::CertSetCertificateContextProperty( shTempCert.get(), CERT_KEY_PROV_INFO_PROP_ID, 0, NULL ) );
|
|
|
|
++i;
|
|
};
|
|
}
|
|
|
|
|
|
|
|
void CIISSite::Close()
|
|
{
|
|
if ( m_hSiteHandle != NULL )
|
|
{
|
|
_ASSERT( m_spIABO != NULL );
|
|
|
|
VERIFY( SUCCEEDED( m_spIABO->CloseKey( m_hSiteHandle ) ) );
|
|
m_spIABO = NULL;
|
|
m_hSiteHandle = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CIISSite::AddKey( LPCWSTR wszKey )const
|
|
{
|
|
_ASSERT( ( m_spIABO != NULL ) && ( m_hSiteHandle != NULL ) );
|
|
_ASSERT( ( wszKey != NULL ) && ( ::wcslen( wszKey ) > 0 ) );
|
|
|
|
HRESULT hr = m_spIABO->AddKey( m_hSiteHandle, wszKey );
|
|
|
|
if ( FAILED( hr ) && ( hr != HRESULT_FROM_WIN32( ERROR_ALREADY_EXISTS ) ))
|
|
{
|
|
throw CObjectException( IDS_E_MD_ADDKEY, wszKey, hr );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Writes a MD record to the XML
|
|
*/
|
|
void CIISSite::ExportMetaRecord( const IXMLDOMDocumentPtr& spDoc,
|
|
const IXMLDOMElementPtr& spKey,
|
|
HCRYPTKEY hCryptKey,
|
|
const METADATA_GETALL_RECORD& Data,
|
|
void* pvData )const
|
|
{
|
|
_ASSERT( spDoc != NULL );
|
|
_ASSERT( spKey != NULL );
|
|
|
|
// Skip this types of metadata:
|
|
// 1. Volitile data
|
|
if ( Data.dwMDAttributes & METADATA_VOLATILE ) return;
|
|
|
|
// We handle only these types of attributes - METADATA_SECURE, METADATA_INHERIT
|
|
// All other should not exist
|
|
_ASSERT( ( Data.dwMDAttributes & METADATA_SECURE ) ||
|
|
( Data.dwMDAttributes & METADATA_INHERIT ) ||
|
|
( Data.dwMDAttributes == METADATA_NO_ATTRIBUTES ) );
|
|
|
|
// Encrypt secure data if we need to
|
|
if ( ( hCryptKey != NULL ) && ( Data.dwMDAttributes & METADATA_SECURE ) )
|
|
{
|
|
DWORD dwSize = Data.dwMDDataLen;
|
|
|
|
IF_FAILED_BOOL_THROW( ::CryptEncrypt( hCryptKey,
|
|
NULL,
|
|
TRUE,
|
|
0,
|
|
reinterpret_cast<BYTE*>( pvData ),
|
|
&dwSize,
|
|
Data.dwMDDataLen ),
|
|
CBaseException( IDS_E_CRYPT_ENCRYPT ) );
|
|
_ASSERT( dwSize == Data.dwMDDataLen );
|
|
}
|
|
|
|
IXMLDOMElementPtr spEl;
|
|
|
|
// Create the node
|
|
// 1. Empty data
|
|
if ( ( NULL == pvData ) || ( 0 == Data.dwMDDataLen ) )
|
|
{
|
|
spEl = CXMLTools::AddTextNode( spDoc, spKey, L"Custom", L"" );
|
|
}
|
|
// 2. Secure and binary data ( secure data is written in binary format )
|
|
else if ( ( BINARY_METADATA == Data.dwMDDataType ) || ( Data.dwMDAttributes & METADATA_SECURE ) )
|
|
{
|
|
spEl = CXMLTools::AddTextNode( spDoc,
|
|
spKey,
|
|
L"Custom",
|
|
Convert::ToString( reinterpret_cast<BYTE*>( pvData ), Data.dwMDDataLen ).c_str() );
|
|
}
|
|
else if ( DWORD_METADATA == Data.dwMDDataType )
|
|
{
|
|
_ASSERT( sizeof( DWORD ) == Data.dwMDDataLen );
|
|
spEl = CXMLTools::AddTextNode( spDoc,
|
|
spKey,
|
|
L"Custom",
|
|
Convert::ToString( *( reinterpret_cast<DWORD*>( pvData ) ) ).c_str() );
|
|
}
|
|
else if ( STRING_METADATA == Data.dwMDDataType )
|
|
{
|
|
spEl = CXMLTools::AddTextNode( spDoc, spKey, L"Custom", reinterpret_cast<LPCWSTR>( pvData ) );
|
|
}
|
|
else if ( ( MULTISZ_METADATA == Data.dwMDDataType ) || ( EXPANDSZ_METADATA == Data.dwMDDataType ) )
|
|
{
|
|
// Convert to data to single string with spaces instead of string terminators
|
|
LPWSTR wszData = reinterpret_cast<LPWSTR>( pvData );
|
|
MultiStrToString( /*r*/wszData );
|
|
|
|
spEl = CXMLTools::AddTextNode( spDoc, spKey, L"Custom", wszData );
|
|
}
|
|
else
|
|
{
|
|
// Unexpected MD type
|
|
_ASSERT( false );
|
|
}
|
|
|
|
// Set the properties
|
|
CXMLTools::SetAttrib( spEl, L"ID", Convert::ToString( Data.dwMDIdentifier ).c_str() );
|
|
CXMLTools::SetAttrib( spEl, L"UserType", Convert::ToString( Data.dwMDUserType ).c_str() );
|
|
CXMLTools::SetAttrib( spEl, L"Type", Convert::ToString( Data.dwMDDataType ).c_str() );
|
|
CXMLTools::SetAttrib( spEl, L"Attributes", Convert::ToString( Data.dwMDAttributes ).c_str() );
|
|
}
|
|
|
|
|
|
/*
|
|
Remove from the config XML all data that should not be imported
|
|
spRoot is expected to be the <WebSite> node
|
|
*/
|
|
void CIISSite::RemoveLocalMetadata( const IXMLDOMElementPtr& spRoot )const
|
|
{
|
|
struct _Helper
|
|
{
|
|
LPCWSTR wszPath;
|
|
DWORD dwID;
|
|
};
|
|
|
|
// First param is the Path ( the meta key, exactly as it will be written in the 'Location' attribute
|
|
// of the IISConfigObject tag ).
|
|
// Second param = the ID of the property to be removed ( ID attribute of the Custom tag )
|
|
|
|
_Helper aData[] = { { L"", MD_SSL_CERT_HASH }, // Cert hash
|
|
{ L"", MD_SSL_CERT_STORE_NAME } // Cert store name
|
|
};
|
|
|
|
for ( DWORD i = 0; i < ARRAY_SIZE( aData ); ++i )
|
|
{
|
|
WCHAR wszXPath[ 1024 ];
|
|
|
|
::swprintf( wszXPath,
|
|
L"Metadata/IISConfigObject[@Location=\"%s\"]/Custom[@ID=\"%u\"]",
|
|
aData[ i ].wszPath,
|
|
aData[ i ].dwID );
|
|
|
|
CXMLTools::RemoveNodes( spRoot, wszXPath );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CIISSite::ImportMetaValue( const IXMLDOMNodePtr& spValue,
|
|
LPCWSTR wszLocation,
|
|
HCRYPTKEY hDecryptKey )const
|
|
{
|
|
// Location and Decrypt key are valid to be NULL
|
|
_ASSERT( spValue != NULL );
|
|
|
|
METADATA_RECORD md = { 0 };
|
|
|
|
md.dwMDIdentifier = Convert::ToDWORD( CXMLTools::GetAttrib( spValue, L"ID" ).c_str() );
|
|
md.dwMDAttributes = Convert::ToDWORD( CXMLTools::GetAttrib( spValue, L"Attributes" ).c_str() );
|
|
md.dwMDDataType = Convert::ToDWORD( CXMLTools::GetAttrib( spValue, L"Type" ).c_str() );
|
|
md.dwMDUserType = Convert::ToDWORD( CXMLTools::GetAttrib( spValue, L"UserType" ).c_str() );
|
|
|
|
CComBSTR bstrData;
|
|
IF_FAILED_HR_THROW( spValue->get_text( &bstrData ),
|
|
CBaseException( IDS_E_XML_PARSE ) );
|
|
|
|
// If the data is secure and we have DecryptKey - then it was encrypted and we should decrypt it
|
|
if ( ( md.dwMDAttributes & METADATA_SECURE ) && ( hDecryptKey != NULL ) )
|
|
{
|
|
// This will decrypt the data in-place
|
|
DecryptData( hDecryptKey, /*r*/bstrData.m_str );
|
|
}
|
|
|
|
TByteAutoPtr spBinData;
|
|
DWORD dwDWORDData = 0;
|
|
|
|
// There may not be any data. Just the key
|
|
if ( bstrData.Length() > 0 )
|
|
{
|
|
DWORD dwMultiSzLen = 0;
|
|
|
|
switch( md.dwMDDataType )
|
|
{
|
|
case BINARY_METADATA:
|
|
Convert::ToBLOB( bstrData.m_str, /*r*/spBinData, /*r*/md.dwMDDataLen );
|
|
md.pbMDData = spBinData.get();
|
|
break;
|
|
|
|
case DWORD_METADATA:
|
|
dwDWORDData = Convert::ToDWORD( bstrData.m_str );
|
|
md.pbMDData = reinterpret_cast<BYTE*>( &dwDWORDData );
|
|
md.dwMDDataLen = sizeof( DWORD );
|
|
break;
|
|
|
|
case STRING_METADATA:
|
|
md.pbMDData = reinterpret_cast<BYTE*>( bstrData.m_str );
|
|
md.dwMDDataLen = static_cast<DWORD>( ( ::wcslen( bstrData ) + 1 ) * sizeof( WCHAR ) );
|
|
break;
|
|
|
|
case MULTISZ_METADATA:
|
|
// Multistrings are stored in the XML separated with space
|
|
// Convert this to strings, separated with '\0' and the whole sequence must
|
|
// be terminated with double '\0'
|
|
|
|
XMLToMultiSz( /*r*/bstrData, dwMultiSzLen );
|
|
md.pbMDData = reinterpret_cast<BYTE*>( bstrData.m_str );
|
|
md.dwMDDataLen = dwMultiSzLen * sizeof( WCHAR );
|
|
break;
|
|
|
|
default:
|
|
_ASSERT( false );
|
|
};
|
|
}//if ( bstrData.Length() > 0 )
|
|
else
|
|
{
|
|
// Empty data. However we need a valid pointer
|
|
// Use the DWORD var
|
|
md.pbMDData = reinterpret_cast<BYTE*>( &dwDWORDData );
|
|
md.dwMDDataLen = 0;
|
|
}
|
|
|
|
// Set the data in the metabase
|
|
IF_FAILED_HR_THROW( m_spIABO->SetData( m_hSiteHandle,
|
|
wszLocation,
|
|
&md ),
|
|
CObjectException( IDS_E_METABASE, wszLocation ) );
|
|
}
|
|
|
|
|
|
|
|
void CIISSite::MultiStrToString( LPWSTR wszData )const
|
|
{
|
|
_ASSERT( wszData != NULL );
|
|
|
|
LPWSTR wszString = wszData;
|
|
|
|
// Replace each '\0' with space. leave only the final one
|
|
bool bExit = false;
|
|
do
|
|
{
|
|
if ( L'\0' == *wszString )
|
|
{
|
|
*wszString = L' ';
|
|
bExit = *( wszString + 1 ) == L'\0';
|
|
}
|
|
|
|
++wszString;
|
|
}while( !bExit );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void CIISSite::XMLToMultiSz( CComBSTR& rbstrData, DWORD& rdwSize )const
|
|
{
|
|
_ASSERT( rbstrData != NULL );
|
|
|
|
// We need one more '\0' at the end of the string, 'cause the sequence must
|
|
// be double '\0' terminated
|
|
|
|
// This will reallocate the buffer ( the buffer will be one more symbol wider )
|
|
// and will add one more '\0' at the end
|
|
DWORD dwSize = static_cast<DWORD>( ::wcslen( rbstrData ) + 2 );
|
|
BSTR bstrNew = ::SysAllocStringLen( rbstrData, dwSize - 1 ); // This fun adds one smore char. see docs for details
|
|
|
|
if ( NULL == bstrNew ) throw CBaseException( IDS_E_OUTOFMEM, ERROR_SUCCESS );
|
|
|
|
// Convert all spaces ( ' ' ) to '\0'
|
|
DWORD iChar = 0;
|
|
while( bstrNew[ iChar ] != L'\0' )
|
|
{
|
|
if ( L' ' == bstrNew[ iChar ] )
|
|
{
|
|
bstrNew[ iChar ] = L'\0';
|
|
}
|
|
|
|
++iChar;
|
|
};
|
|
|
|
// Assign the new value to the result
|
|
// Operator = cannot be used as it will do a SysAllocString and thus, the final '\0' will be lost
|
|
rbstrData.Empty();
|
|
rbstrData.Attach( bstrNew );
|
|
rdwSize = dwSize;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|