TDCCTL.CPP
//------------------------------------------------------------------------ 
// 
//  Tabular Data Control 
//  Copyright (C) Microsoft Corporation, 1996, 1997 
// 
//  File:       TDCCtl.cpp 
// 
//  Contents:   Implementation of the CTDCCtl ActiveX control. 
// 
//------------------------------------------------------------------------ 
 
#include "stdafx.h" 
#include "STD.h" 
#include "TDCIds.h" 
#include "TDC.h" 
#include <MLang.h> 
#include "Notify.h" 
#include "TDCParse.h" 
#include "TDCArr.h" 
#include "SimpData.h" 
#include "TDCCtl.h" 
#include "locale.h" 
#include "langcode.h" 
 
//------------------------------------------------------------------------ 
// 
//  Function:   EmptyBSTR() 
// 
//  Synopsis:   Indicates whether the given BSTR object represents an 
//              empty string. 
// 
//  Arguments:  bstr     String to test 
// 
//  Returns:    TRUE if 'bstr' represents an empty string 
//              FALSE otherwise. 
// 
//------------------------------------------------------------------------ 
 
inline boolean EmptyBSTR(BSTR bstr) 
{ 
    return bstr == NULL || bstr[0] == 0; 
} 
 
void 
ClearInterfaceFn(IUnknown ** ppUnk) 
{ 
    IUnknown * pUnk; 
 
    pUnk = *ppUnk; 
    *ppUnk = NULL; 
    if (pUnk) 
        pUnk->Release(); 
} 
 
//------------------------------------------------------------------------ 
// 
//  Method:     CTDCCtl() 
// 
//  Synopsis:   Class constructor 
// 
//  Arguments:  None 
// 
//------------------------------------------------------------------------ 
 
CTDCCtl::CTDCCtl() 
{ 
    m_cbstrFieldDelim = DEFAULT_FIELD_DELIM; 
    m_cbstrRowDelim = DEFAULT_ROW_DELIM; 
    m_cbstrQuoteChar = DEFAULT_QUOTE_CHAR; 
    m_fUseHeader = VARIANT_FALSE; 
    m_fSortAscending = VARIANT_TRUE; 
    m_fAppendData = VARIANT_FALSE; 
    m_pSTD = NULL; 
    m_pArr = NULL; 
    m_pUnify = NULL; 
    m_pEventBroker = new CEventBroker(this); 
    m_pDataSourceListener = NULL; 
// ;begin_internal 
    m_pDATASRCListener = NULL; 
// ;end_internal 
    m_pBSC = NULL; 
    m_enumFilterCriterion = (OSPCOMP) 0; 
    m_fDataURLChanged = FALSE; 
    m_lTimer = 0; 
    m_fCaseSensitive = VARIANT_TRUE; 
    m_hrDownloadStatus = S_OK; 
    m_fInReset = FALSE; 
 
    //  Create an MLANG object 
    // 
    m_nCodePage = 0;                    // use default from host 
    { 
        HRESULT hr; 
 
        m_pML = NULL; 
        hr = CoCreateInstance(CLSID_CMultiLanguage, NULL, 
                              CLSCTX_INPROC_SERVER, IID_IMultiLanguage, 
                              (void**) &m_pML); 
        // Don't set the default Charset here.  Leave m_nCodepage set 
        // to 0 to indicate default charset.  Later we'll try to query 
        // our host's default charset, and failing that we'll use CP_ACP. 
        _ASSERTE(SUCCEEDED(hr) && m_pML != NULL);         
    } 
 
    m_lcidRead = 0x0000;                // use default from host 
} 
 
 
//------------------------------------------------------------------------ 
// 
//  Method:     ~CTDCCtl() 
// 
//  Synopsis:   Class destructor 
// 
//------------------------------------------------------------------------ 
 
CTDCCtl::~CTDCCtl() 
{ 
    ULONG cRef = _ThreadModel::Decrement(&m_dwRef); 
 
    ClearInterface(&m_pSTD); 
 
    if (cRef ==0) 
    { 
        TimerOff(); 
        ReleaseTDCArr(FALSE); 
 
        if (m_pEventBroker) 
        { 
            m_pEventBroker->Release(); 
            m_pEventBroker = NULL; 
        } 
        ClearInterface(&m_pDataSourceListener); 
// ;begin_internal 
        ClearInterface(&m_pDATASRCListener); 
// ;end_internal 
        ClearInterface(&m_pML); 
    } 
} 
 
//------------------------------------------------------------------------ 
// 
//  These set/get methods implement the control's properties, 
//  copying values to and from class members.  They perform no 
//  other processing apart from argument validation. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP CTDCCtl::get_ReadyState(LONG *plReadyState) 
{ 
    HRESULT hr; 
 
    if (m_pEventBroker == NULL) 
    { 
        // We must provide a ReadyState whether we want to or not, or our 
        // host can never go COMPLETE. 
        *plReadyState = READYSTATE_COMPLETE; 
        hr = S_OK; 
    } 
    else 
        hr = m_pEventBroker->GetReadyState(plReadyState); 
    return hr; 
} 
 
STDMETHODIMP CTDCCtl::put_ReadyState(LONG lReadyState) 
{ 
    // We don't allow setting of Ready State, but take advantage of a little 
    // kludge here to update our container's impression of our readystate 
    FireOnChanged(DISPID_READYSTATE);     
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_FieldDelim(BSTR* pbstrFieldDelim) 
{ 
    *pbstrFieldDelim = m_cbstrFieldDelim.Copy(); 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_FieldDelim(BSTR bstrFieldDelim) 
{ 
    HRESULT hr = S_OK; 
 
    if (bstrFieldDelim == NULL || bstrFieldDelim[0] == 0) 
    { 
        m_cbstrFieldDelim = DEFAULT_FIELD_DELIM; 
        if (m_cbstrFieldDelim == NULL) 
            hr = E_OUTOFMEMORY; 
    } 
    else 
        m_cbstrFieldDelim = bstrFieldDelim; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_RowDelim(BSTR* pbstrRowDelim) 
{ 
    *pbstrRowDelim = m_cbstrRowDelim.Copy(); 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_RowDelim(BSTR bstrRowDelim) 
{ 
    HRESULT hr = S_OK; 
 
    if (bstrRowDelim == NULL || bstrRowDelim[0] == 0) 
    { 
        m_cbstrRowDelim = DEFAULT_ROW_DELIM; 
        if (m_cbstrRowDelim == NULL) 
            hr = E_OUTOFMEMORY; 
    } 
    else 
        m_cbstrRowDelim = bstrRowDelim; 
    return hr; 
} 
 
STDMETHODIMP CTDCCtl::get_TextQualifier(BSTR* pbstrTextQualifier) 
{ 
    *pbstrTextQualifier = m_cbstrQuoteChar.Copy(); 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_TextQualifier(BSTR bstrTextQualifier) 
{ 
    m_cbstrQuoteChar = bstrTextQualifier; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_EscapeChar(BSTR* pbstrEscapeChar) 
{ 
    *pbstrEscapeChar = m_cbstrEscapeChar.Copy(); 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_EscapeChar(BSTR bstrEscapeChar) 
{ 
    m_cbstrEscapeChar = bstrEscapeChar; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_UseHeader(VARIANT_BOOL* pfUseHeader) 
{ 
    *pfUseHeader = m_fUseHeader; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_UseHeader(VARIANT_BOOL fUseHeader) 
{ 
    m_fUseHeader = fUseHeader; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_SortColumn(BSTR* pbstrSortColumn) 
{ 
    *pbstrSortColumn = m_cbstrSortColumn.Copy(); 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_SortColumn(BSTR bstrSortColumn) 
{ 
    m_cbstrSortColumn = bstrSortColumn; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_SortAscending(VARIANT_BOOL* pfSortAscending) 
{ 
    *pfSortAscending = m_fSortAscending; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_SortAscending(VARIANT_BOOL fSortAscending) 
{ 
    m_fSortAscending = fSortAscending; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_FilterValue(BSTR* pbstrFilterValue) 
{ 
    *pbstrFilterValue = m_cbstrFilterValue.Copy(); 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_FilterValue(BSTR bstrFilterValue) 
{ 
    m_cbstrFilterValue = bstrFilterValue; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_FilterCriterion(BSTR* pbstrFilterCriterion) 
{ 
    HRESULT hr; 
    WCHAR   *pwchCriterion; 
 
    switch (m_enumFilterCriterion) 
    { 
    case OSPCOMP_EQ:    pwchCriterion = L"=";   break; 
    case OSPCOMP_LT:    pwchCriterion = L"<";   break; 
    case OSPCOMP_LE:    pwchCriterion = L"<=";  break; 
    case OSPCOMP_GE:    pwchCriterion = L">=";  break; 
    case OSPCOMP_GT:    pwchCriterion = L">";   break; 
    case OSPCOMP_NE:    pwchCriterion = L"<>";  break; 
    default:            pwchCriterion = L"??";  break; 
    } 
    *pbstrFilterCriterion = SysAllocString(pwchCriterion); 
    hr = (*pbstrFilterCriterion == NULL) ? E_OUTOFMEMORY : S_OK; 
 
    return hr; 
} 
 
STDMETHODIMP CTDCCtl::put_FilterCriterion(BSTR bstrFilterCriterion) 
{ 
    m_enumFilterCriterion = (OSPCOMP) 0; 
    if (bstrFilterCriterion != NULL) 
    { 
        switch (bstrFilterCriterion[0]) 
        { 
        case L'<': 
            if (bstrFilterCriterion[1] == 0) 
                m_enumFilterCriterion = OSPCOMP_LT; 
            else if (bstrFilterCriterion[2] == 0) 
            { 
                if (bstrFilterCriterion[1] == L'>') 
                    m_enumFilterCriterion = OSPCOMP_NE; 
                else if (bstrFilterCriterion[1] == L'=') 
                    m_enumFilterCriterion = OSPCOMP_LE; 
            } 
            break; 
        case L'>': 
            if (bstrFilterCriterion[1] == 0) 
                m_enumFilterCriterion = OSPCOMP_GT; 
            else if (bstrFilterCriterion[1] == L'=' && bstrFilterCriterion[2] == 0) 
                m_enumFilterCriterion = OSPCOMP_GE; 
            break; 
        case L'=': 
            if (bstrFilterCriterion[1] == 0) 
                m_enumFilterCriterion = OSPCOMP_EQ; 
            break; 
        } 
    } 
 
    //  Return SUCCESS, even on an invalid value; otherwise the 
    //  frameworks using the control will panic and abandon all hope. 
    // 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_FilterColumn(BSTR* pbstrFilterColumn) 
{ 
    *pbstrFilterColumn = m_cbstrFilterColumn.Copy(); 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_FilterColumn(BSTR bstrFilterColumn) 
{ 
    m_cbstrFilterColumn = bstrFilterColumn; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_CharSet(BSTR* pbstrCharSet) 
{ 
    HRESULT hr = E_FAIL; 
 
    if (m_pML != NULL) 
    { 
        MIMECPINFO  info; 
 
        hr = m_pML->GetCodePageInfo(m_nCodePage, &info); 
        if (SUCCEEDED(hr)) 
        { 
            *pbstrCharSet = SysAllocString(info.wszWebCharset); 
            if (*pbstrCharSet == NULL) 
                hr = E_OUTOFMEMORY; 
        } 
    } 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_CharSet(BSTR bstrCharSet) 
{ 
    HRESULT hr = E_FAIL; 
 
    if (m_pML != NULL) 
    { 
        MIMECSETINFO    info; 
 
        hr = m_pML->GetCharsetInfo(bstrCharSet, &info); 
        if (SUCCEEDED(hr)) 
        { 
            m_nCodePage = info.uiCodePage; 
            if (m_nCodePage == 0) 
                m_nCodePage = info.uiInternetEncoding; 
        } 
    } 
    return hr; 
} 
 
STDMETHODIMP CTDCCtl::get_Language(BSTR* pbstrLanguage) 
{ 
    if (m_pArr) 
    { 
        return m_pArr->getLocale(pbstrLanguage); 
    } 
     
    *pbstrLanguage = m_cbstrLanguage.Copy(); 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_Language_(LPWCH pwchLanguage) 
{ 
    HRESULT hr  = S_OK; 
    LCID    lcid; 
 
    hr = m_pML->GetLcidFromRfc1766(&lcid, pwchLanguage); 
    if (SUCCEEDED(hr)) 
    { 
        m_cbstrLanguage = pwchLanguage; 
        m_lcidRead = lcid; 
    } 
    return hr; 
} 
 
STDMETHODIMP CTDCCtl::put_Language(BSTR bstrLanguage) 
{ 
    return put_Language_(bstrLanguage); 
} 
 
STDMETHODIMP CTDCCtl::get_DataURL(BSTR* pbstrDataURL) 
{ 
    *pbstrDataURL = m_cbstrDataURL.Copy(); 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_DataURL(BSTR bstrDataURL) 
{ 
    HRESULT hr = S_OK; 
 
    m_cbstrDataURL = bstrDataURL; 
    m_fDataURLChanged = TRUE; 
    return hr; 
} 
 
// ;begin_internal 
#ifdef NEVER 
STDMETHODIMP CTDCCtl::get_RefreshInterval(LONG* plTimer) 
{ 
    *plTimer = m_lTimer; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_RefreshInterval(LONG lTimer) 
{ 
    m_lTimer = lTimer; 
    if (m_lTimer > 0) 
        TimerOn(m_lTimer * 1000); 
    else 
        TimerOff(); 
    return S_OK; 
} 
#endif 
// ;end_internal 
 
STDMETHODIMP CTDCCtl::get_Filter(BSTR* pbstrFilterExpr) 
{ 
    *pbstrFilterExpr = m_cbstrFilterExpr.Copy(); 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_Filter(BSTR bstrFilterExpr) 
{ 
    m_cbstrFilterExpr = bstrFilterExpr; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_Sort(BSTR* pbstrSortExpr) 
{ 
    *pbstrSortExpr = m_cbstrSortExpr.Copy(); 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_Sort(BSTR bstrSortExpr) 
{ 
    m_cbstrSortExpr = bstrSortExpr; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_AppendData(VARIANT_BOOL* pfAppendData) 
{ 
    *pfAppendData = m_fAppendData; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_AppendData(VARIANT_BOOL fAppendData) 
{ 
    m_fAppendData = fAppendData; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_CaseSensitive(VARIANT_BOOL* pfCaseSensitive) 
{ 
    *pfCaseSensitive = m_fCaseSensitive; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::put_CaseSensitive(VARIANT_BOOL fCaseSensitive) 
{ 
    m_fCaseSensitive = fCaseSensitive; 
    return S_OK; 
} 
 
STDMETHODIMP CTDCCtl::get_OSP(OLEDBSimpleProviderX ** ppISTD) 
{ 
    // Return an OSP if we have one, but don't create one on demand! 
    // (Otherwise property bag load stuff will cause us to create an 
    // OSP prematurely). 
    *ppISTD = NULL; 
    if (m_pSTD) 
    { 
        *ppISTD = (OLEDBSimpleProviderX *)m_pSTD; 
    } 
    return S_OK; 
} 
 
 
//------------------------------------------------------------------------ 
// 
//  Method:    UpdateReadyState 
// 
//  Synopsis:  Vectors to the event brokers ReadyState, if there is one. 
// ;begin_internal 
//             Note, we have to be able to set our readystate and fire change 
//             events on it, whether or not creation of the broker succeeded, 
//             or we prevent our host container from reaching 
//             READYSTATE_COMPLETE, which is not acceptable.  We therefore 
//             have to duplicate some of the broker's work here.  This makes 
//             me wonder whether the broker architecture was a good idea. 
// ;end_internal 
// 
//  Arguments: None. 
// 
//  Returns:   S_OK upon success. 
//             Error codes as per Reset() upon error. 
// 
//------------------------------------------------------------------------ 
void 
CTDCCtl::UpdateReadyState(LONG lReadyState) 
{ 
    if (m_pEventBroker) 
        m_pEventBroker->UpdateReadyState(lReadyState); 
    else 
    { 
        // We have no broker, but our host is still waiting for us to 
        // go READYSTATE_COMPLETE.  We fire the OnChange here noting that 
        // get_ReadyState with no broker will return COMPLETE. 
        FireOnChanged(DISPID_READYSTATE); 
        FireOnReadyStateChanged(); 
    } 
} 
 
//------------------------------------------------------------------------ 
// 
//  Method:    _OnTimer() 
// 
//  Synopsis:  Handles an internal timer event by refreshing the control. 
// 
//  Arguments: None. 
// 
//  Returns:   S_OK upon success. 
//             Error codes as per Reset() upon error. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP CTDCCtl::_OnTimer() 
{ 
    HRESULT hr = S_OK; 
 
    if (m_pArr != NULL && m_pArr->GetLoadState() == CTDCArr::LS_LOADED) 
    { 
        m_fDataURLChanged = TRUE; 
        hr = Reset(); 
    } 
 
    return hr; 
} 
 
 
//------------------------------------------------------------------------ 
// 
//  Method:    msDataSourceObject() 
// 
//  Synopsis:  Yields an ISimpleTabularData interface for this control. 
//             If this is the first call, a load operation is initiated 
//             reading data from the control's specified DataURL property. 
//             An STD object is created to point to the control's embedded 
//             TDCArr object. 
// 
//  Arguments: qualifier     Ignored - must be an empty BSTR. 
//             ppUnk         Pointer to returned interface  [OUT] 
// 
//  Returns:   S_OK upon success. 
//             E_INVALIDARG if 'qualifier' isn't an empty BSTR. 
//             E_OUTOFMEMORY if non enough memory could be allocated to 
//               complete the construction of the interface. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCCtl::msDataSourceObject(BSTR qualifier, IUnknown **ppUnk) 
{ 
    HRESULT hr  = S_OK; 
 
    *ppUnk = NULL;                      // NULL in case of failure 
 
    if (!EmptyBSTR(qualifier)) 
    { 
        hr = E_INVALIDARG; 
        goto error; 
    } 
 
    // Was there a previous attempt to load this page that failed? 
    // (Probably due to security or file not found or something). 
    if (m_hrDownloadStatus) 
    { 
        hr = m_hrDownloadStatus; 
        goto error; 
    } 
 
    if (m_pArr == NULL) 
    { 
        // We don't have a valid TDC to give back, probably have to try 
        // downloading one. 
        UpdateReadyState(READYSTATE_LOADED); 
        hr = CreateTDCArr(FALSE); 
        if (hr) 
            goto error; 
    } 
 
    _ASSERTE(m_pArr != NULL); 
 
    if (m_pSTD == NULL) 
    { 
        OutputDebugStringX(_T("Creating an STD COM object\n")); 
 
        // fetch ISimpleTabularData interface pointer 
        m_pArr->QueryInterface(IID_OLEDBSimpleProvider, (void**)&m_pSTD); 
        _ASSERTE(m_pSTD != NULL); 
    } 
 
    // Return the STD if we have one, otherwise it stays NULL 
    if (m_pSTD && m_pArr->GetLoadState() >= CTDCArr::LS_LOADING_HEADER_AVAILABLE) 
    { 
        *ppUnk = (OLEDBSimpleProviderX *) m_pSTD; 
        m_pSTD->AddRef();           // We must AddRef the STD we return! 
    } 
 
cleanup: 
    return hr; 
 
error: 
    UpdateReadyState(READYSTATE_COMPLETE); 
    goto cleanup; 
} 
 
 
// Override IPersistPropertyBagImpl::Load 
STDMETHODIMP 
CTDCCtl::Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog) 
{ 
    HRESULT hr; 
    IUnknown *pSTD; 
     
    // Do normal load 
    // IPersistPropertyBagImpl<CTDCCtl> 
    hr = IPersistPropertyBagImpl<CTDCCtl>::Load(pPropBag, pErrorLog); 
     
    // and then start download, if we can 
    (void)msDataSourceObject(NULL, &pSTD); 
 
    // If we actually got an STD, we should release it.  This won't really 
    // make it go away, since we still have the ref from the QI.  This is 
    // a bit of a kludge that we should clean up later. 
    ClearInterface(&pSTD); 
 
    return hr; 
} 
 
 
//------------------------------------------------------------------------ 
// 
//  Method:    CreateTDCArr() 
// 
//  Synopsis:  Creates the control's embedded TDCArr object. 
//             Initiates a data download from the DataURL property. 
// 
//  Arguments: fAppend         Flag indicating whether data should be 
//                             appended to an existing TDC object. 
// 
//  Returns:   S_OK upon success. 
//             E_OUTOFMEMORY if non enough memory could be allocated to 
//               complete the construction of the TDCArr object. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCCtl::CreateTDCArr(boolean fAppend) 
{ 
    HRESULT hr  = S_OK; 
 
    if (m_pEventBroker == NULL) 
    { 
        hr = E_FAIL; 
        goto Error; 
    } 
 
    // Iff we're appending is m_pArr allowed to be non-null here. 
    _ASSERT ((m_pArr != NULL) == fAppend); 
 
    if (m_pArr == NULL) 
    { 
        m_pArr = new CTDCArr(); 
        if (m_pArr == NULL) 
        { 
            hr = E_OUTOFMEMORY; 
            goto Error; 
        } 
 
        hr = m_pArr->Init(m_pEventBroker, m_pML); 
        if (FAILED(hr)) 
            goto Error; 
    } 
 
    hr = InitiateDataLoad(fAppend); 
    if (hr) 
        goto Error; 
 
    // We decide something is not async if it finished loading during 
    // the InitiateDataLoad call. 
    m_pArr->SetIsAsync(!(m_pArr->GetLoadState()==CTDCArr::LS_LOADED)); 
 
Cleanup: 
    return hr; 
 
Error: 
    if (!fAppend) 
    { 
        ClearInterface(&m_pArr); 
    } 
    goto Cleanup; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Method:    ReleaseTDCArr() 
// 
//  Synopsis:  Releases the control's embedded TDCArr object. 
//             Releases the control's CTDCUnify and CTDCTokenise objects. 
//             Releases the old event broker and re-creates it if replacing. 
// 
//  Arguments: fReplacingTDCArr   Flag indicating whether a new TDCArr object 
//                                will be created. 
// 
//  Returns:   S_OK upon success. 
//             Error code upon failure. 
//             E_OUTOFMEMORY if non enough memory could be allocated to 
//               complete the construction of the new CEventBroker object. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCCtl::ReleaseTDCArr(boolean fReplacingTDCArr) 
{ 
    HRESULT hr = S_OK; 
 
    TerminateDataLoad(); 
 
    //  Release the reference to the current TDCArr object 
    // 
    if (m_pArr != NULL) 
    { 
        m_pArr->Release(); 
        m_pArr = NULL; 
 
        // Since we've shut down the CTDCArr object, we should release 
        // it's OLEDBSimplerProviderListener sink. 
        if (m_pEventBroker) 
        { 
            m_pEventBroker->SetSTDEvents(NULL); 
        } 
 
        if (fReplacingTDCArr) 
        { 
            // Release our previous Event Broker. 
            if (m_pEventBroker) 
            { 
                m_pEventBroker->Release(); 
                m_pEventBroker = NULL; 
            } 
 
            //  Create a new event broker. 
            m_pEventBroker = new CEventBroker(this); 
            if (m_pEventBroker == NULL) 
            { 
                hr = E_OUTOFMEMORY; 
                goto Cleanup; 
            } 
             
            // Set the DataSourceListener for the new event broker. 
            m_pEventBroker->SetDataSourceListener(m_pDataSourceListener); 
 
// ;begin_internal 
            m_pEventBroker->SetDATASRCListener(m_pDATASRCListener); 
// ;end_internal 
        } 
    } 
 
Cleanup: 
    return hr; 
} 
 
const IID IID_IDATASRCListener = {0x3050f380,0x98b5,0x11cf,{0xbb,0x82,0x00,0xaa,0x00,0xbd,0xce,0x0b}}; 
const IID IID_DataSourceListener = {0x7c0ffab2,0xcd84,0x11d0,{0x94,0x9a,0x00,0xa0,0xc9,0x11,0x10,0xed}}; 
 
//------------------------------------------------------------------------ 
// 
//  Method:    addDataSourceListener() 
// 
//  Synopsis:  Sets the COM object which should receive notification 
//             events. 
// 
//  Arguments: pEvent        Pointer to COM object to receive notification 
//                           events, or NULL if no notifications to be sent. 
// 
//  Returns:   S_OK upon success. 
//             Error code upon failure. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP 
CTDCCtl::addDataSourceListener(IUnknown *pListener) 
{ 
 
    if (m_pEventBroker != NULL) 
    { 
        HRESULT hr = S_OK;         
        IUnknown * pDatasrcListener; 
 
        // Make sure this is the interface we expect 
        hr = pListener->QueryInterface(IID_DataSourceListener, 
                                       (void **)&pDatasrcListener); 
        if (SUCCEEDED(hr)) 
        { 
            m_pEventBroker-> 
                    SetDataSourceListener((DataSourceListener *)pDatasrcListener); 
 
            // Clear any previous 
            ClearInterface (&m_pDataSourceListener); 
            // and remember the new. 
            m_pDataSourceListener = (DataSourceListener *)pDatasrcListener; 
        } 
// ;begin_internal 
        else 
        { 
            // The definition of this interface was changed from IDATASRCListener to 
            // DataSourceListener.  To make sure we don't cause crashes, we QI to 
            // determine which one we were handed. 
            hr = pListener->QueryInterface(IID_IDATASRCListener, 
                                           (void **)&pDatasrcListener); 
            if (SUCCEEDED(hr)) 
            { 
                m_pEventBroker-> 
                        SetDATASRCListener((DATASRCListener *) pDatasrcListener); 
 
                // Clear any previous 
                ClearInterface (&m_pDATASRCListener); 
                // and remember the new. 
                m_pDATASRCListener = (DATASRCListener *)pDatasrcListener; 
            } 
        } 
// ;end_internal 
        return hr; 
    } 
    else 
        return E_FAIL; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Method:    Reset() 
// 
//  Synopsis:  Reset the control's filter/sort criteria. 
// 
//  Arguments: None. 
// 
//  Returns:   S_OK upon success. 
//             Error code upon failure. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP CTDCCtl::Reset() 
{ 
    HRESULT hr  = S_OK; 
 
    // The next query to msDataSourceObject should get a new STD 
    ClearInterface(&m_pSTD); 
 
    // Infinite recursive calls to Reset can occur if script code calls reset 
    // from within the datasetchanged event.  This isn't a good idea. 
    if (m_fInReset) 
    { 
        hr = E_FAIL; 
        goto Cleanup; 
    } 
 
    m_fInReset = TRUE; 
 
    // Clear any previous error 
    m_hrDownloadStatus = S_OK; 
 
    if (m_fDataURLChanged) 
    { 
        if (!m_fAppendData) 
        { 
            // Release previous TDC array with "replacing" flag. 
            hr = ReleaseTDCArr(TRUE); 
            if (!SUCCEEDED(hr))         // possible memory failure 
                goto Cleanup; 
        } 
 
        // Read the new data into a TDC arry, appending if specified. 
        hr = CreateTDCArr((BOOL)m_fAppendData); 
    } 
    else if (m_pArr != NULL) 
    { 
        // Re-apply the sort and filter criteria 
        hr = m_pArr->SetSortFilterCriteria(bstrConstructSortExpr(), 
                                           bstrConstructFilterExpr(), 
                                           m_fCaseSensitive ? 1 : 0); 
    } 
 
    m_fInReset = FALSE; 
 
Cleanup: 
    return hr; 
} 
 
 
//------------------------------------------------------------------------ 
// 
//  Method:    bstrConstructSortExpr() 
// 
//  Synopsis:  Constructs a sort expression from the Sort property or 
//             (for backward compatibility) from the SortColumn/SortAscending 
//             properties. 
// 
//             This method only exists to isolate backward-compatibility 
//             with the old-fashioned sort properties. 
// 
//  Arguments: None. 
// 
//  Returns:   The constructed sort expression. 
// 
//  NB!  It is the caller's responsibility to free the string returned. 
// 
//------------------------------------------------------------------------ 
 
BSTR 
CTDCCtl::bstrConstructSortExpr() 
{ 
    BSTR    bstr = NULL; 
 
    if (!EmptyBSTR(m_cbstrSortExpr)) 
        bstr = SysAllocString(m_cbstrSortExpr); 
    else if (!EmptyBSTR(m_cbstrSortColumn)) 
    { 
        //  Use the old-fashioned sort properties 
        //  Construct a sort expression of the form: 
        //     <SortColumn>  or 
        //    -<SortColumn> 
        // 
        if (m_fSortAscending) 
            bstr = SysAllocString(m_cbstrSortColumn); 
        else 
        { 
            bstr = SysAllocStringLen(NULL, SysStringLen(m_cbstrSortColumn) + 1); 
            if (bstr != NULL) 
            { 
                bstr[0] = L'-'; 
                wch_cpy(&bstr[1], m_cbstrSortColumn); 
            } 
        } 
    } 
 
    return bstr; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Method:    bstrConstructFilterExpr() 
// 
//  Synopsis:  Constructs a filter expression from the Filter property or 
//             (for backward compatibility) from the FilterColumn/FilterValue/ 
//             FilterCriterion properties. 
// 
//             This method only exists to isolate backward-compatibility 
//             with the old-fashioned filter properties. 
// 
//  Arguments: None. 
// 
//  Returns:   The constructed filter expression 
// 
//  NB!  It is the caller's responsibility to free the string returned. 
// 
//------------------------------------------------------------------------ 
 
BSTR 
CTDCCtl::bstrConstructFilterExpr() 
{ 
    BSTR    bstr = NULL; 
 
    if (!EmptyBSTR(m_cbstrFilterExpr)) 
        bstr = SysAllocString(m_cbstrFilterExpr); 
    else if (!EmptyBSTR(m_cbstrFilterColumn)) 
    { 
        //  Use the old-fashioned filter properties 
        //  Construct a sort expression of the form: 
        //     <FilterColumn> <FilterCriterion> "<FilterValue>" 
        // 
        BSTR bstrFilterOp; 
        HRESULT hr; 
 
        hr = get_FilterCriterion(&bstrFilterOp); 
        if (!SUCCEEDED(hr)) 
            goto Cleanup; 
        bstr = SysAllocStringLen(NULL, 
                    SysStringLen(m_cbstrFilterColumn) + 
                    SysStringLen(bstrFilterOp) + 
                    1 + 
                    SysStringLen(m_cbstrFilterValue) + 
                    1); 
        if (bstr != NULL) 
        { 
            DWORD pos = 0; 
            wch_cpy(&bstr[pos], m_cbstrFilterColumn); 
            pos = wch_len(bstr); 
            wch_cpy(&bstr[pos], bstrFilterOp); 
            pos = wch_len(bstr); 
            bstr[pos++] = L'"'; 
            wch_cpy(&bstr[pos], m_cbstrFilterValue); 
            pos = wch_len(bstr); 
            bstr[pos++] = L'"'; 
            bstr[pos] = 0; 
        } 
        SysFreeString(bstrFilterOp); 
    } 
Cleanup: 
    return bstr; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Method:    TerminateDataLoad() 
// 
//  Synopsis:  Stop the current data load operation. 
// 
//  Returns:   S_OK upon success. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP CTDCCtl::TerminateDataLoad() 
{ 
    HRESULT hr  = S_OK; 
 
    // Make sure if we call Reset() right away now, we don't re-download 
    // the data. 
    m_fDataURLChanged = FALSE; 
 
    m_pBSC = NULL;      //  Block any outstanding OnData calls 
 
    if (m_pEventBroker) 
        m_pEventBroker->m_pBSC = NULL;  // kill all 
 
    if (m_pUnify != NULL) 
        delete m_pUnify; 
 
    m_pUnify = NULL; 
 
    return hr; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Method:    InitiateDataLoad() 
// 
//  Synopsis:  Start loading data from the control's DataURL property. 
// 
//  Arguments: fAppend        Flag to indicate whether data should be 
//                            appended to an existing TDCArr object. 
// 
//  Returns:   S_OK upon success. 
//             E_OUTOFMEMORY if not enough memory could be allocated to 
//               complete the download. 
// 
//------------------------------------------------------------------------ 
 
STDMETHODIMP CTDCCtl::InitiateDataLoad(boolean fAppend) 
{ 
    HRESULT hr  = S_OK; 
 
    WCHAR   wchFieldDelim = (!m_cbstrFieldDelim) ? 0 : m_cbstrFieldDelim[0]; 
    WCHAR   wchRowDelim   = (!m_cbstrRowDelim)   ? 0 : m_cbstrRowDelim[0]; 
    // Default quote char to double-quote, not NULL 
    WCHAR   wchQuoteChar  = (!m_cbstrQuoteChar)  ? 0 : m_cbstrQuoteChar[0]; 
    WCHAR   wchEscapeChar = (!m_cbstrEscapeChar) ? 0 : m_cbstrEscapeChar[0]; 
 
    // 
    // Figure out what code page to use. 
    // 
    if (0==m_nCodePage)                  
    { 
        // 0 means user didn't set one, so ask our container. 
        VARIANT varCodepage; 
        VariantInit(&varCodepage); 
        GetAmbientProperty(DISPID_AMBIENT_CODEPAGE, varCodepage); 
 
        // If container didn't provide one, use the default code page 
        // of the client system. 
        m_nCodePage = (varCodepage.vt == VT_UI4) 
                        ? (ULONG)varCodepage.lVal 
                        : 1252;         // ultimate default is Latin-1 
    } 
 
    // 
    // Default LCID the same way as Codepage 
    // 
    if (0==m_lcidRead) 
    { 
        hr = GetAmbientLocaleID(m_lcidRead); 
        if (FAILED(hr)) 
        { 
            // Ultimate default is US locale -- sort of Web global 
            // language default. 
            put_Language_(L"en-us"); 
        } 
    } 
 
    if (EmptyBSTR(m_cbstrDataURL)) 
    { 
        hr = S_FALSE;                   // quiet failure 
        goto Error; 
    } 
 
    OutputDebugStringX(_T("Initiating Data Download\n")); 
 
    //  No data load should currently be in progress - 
    //  This data load has been initiated on the construction of a new 
    //  TDCArr object, or appending to an existing loaded TDCArr object. 
    //  Any currently running data load would have been 
    //  terminated by the call to ReleaseTDCArr(). 
    // 
 
    _ASSERT(m_pUnify == NULL); 
    _ASSERT(m_pBSC == NULL); 
 
 
    m_hrDownloadStatus = S_OK; 
 
    //  Create a pipeline of objects to process the URL data 
    // 
    //    CMyBindStatusCallback -> CTDCUnify -> CTDCTokenise -> CTDCArr 
    // 
 
    CComObject<CMyBindStatusCallback<CTDCCtl> >::CreateInstance(&m_pBSC); 
 
    if (m_pBSC == NULL) 
    { 
        hr = E_FAIL; 
        goto Error; 
    } 
    hr = m_pArr->StartDataLoad(m_fUseHeader ? TRUE : FALSE, 
                               bstrConstructSortExpr(), 
                               bstrConstructFilterExpr(), 
                               m_lcidRead, 
                               m_pBSC, 
                               fAppend, 
                               m_fCaseSensitive ? 1 : 0); 
    if (!SUCCEEDED(hr)) 
        goto Error; 
 
    m_pUnify = new CTDCUnify(); 
    if (m_pUnify == NULL) 
    { 
        hr = E_OUTOFMEMORY; 
        goto Error; 
    } 
    m_pUnify->Create(m_nCodePage, m_pML); 
 
    // Init tokenizer 
    m_pUnify->InitTokenizer(m_pArr, wchFieldDelim, wchRowDelim, 
                            wchQuoteChar, wchEscapeChar); 
 
 
 
    m_fSecurityChecked = FALSE; 
 
    // Start (and maybe perform) actual download. 
    // If we're within a Reset() call, always force a "reload" of the data 
    // from the server -- i.e. turn on BINDF_GETNEWESTVERSION to make sure 
    // sure the cache data isn't stale. 
    hr = m_pBSC->StartAsyncDownload(this, OnData, m_cbstrDataURL, m_spClientSite, TRUE, 
                                    m_fInReset == TRUE); 
    if (FAILED(hr)) 
        goto Error; 
 
    // m_hrDownloadStatus remembers the first (if any) error that occured during 
    // the OnData callbacks.  Unlike an error returning from StartAsyncDownload, 
    // this doesn't necessarily cause us to throw away the TDC array. 
    hr = m_hrDownloadStatus; 
    if (!SUCCEEDED(hr)) 
        m_pBSC = NULL; 
 
Cleanup: 
    return hr; 
 
Error: 
    TerminateDataLoad(); 
    if (m_pEventBroker) 
    { 
        // Fire data set changed to indicate query failed, 
        m_pEventBroker->STDDataSetChanged(); 
        // and go complete. 
        UpdateReadyState(READYSTATE_COMPLETE); 
    } 
    goto Cleanup; 
} 
 
// ;begin_internal 
 
#define ZERO '0' 
#define NINE '9' 
#define DOT '.' 
 
// Scans a string and returns TRUE if it looks like a numeric 
// part of an IP address 
static BOOL ScanNumber (LPOLESTR& psz) 
{ 
    // first char 
    if (*psz < ZERO || *psz > NINE) 
        return FALSE; 
    psz++; 
 
    // second char 
    if (*psz < ZERO || *psz > NINE) 
        return FALSE; 
    psz++; 
 
    // third char 
    if (*psz < ZERO || *psz > NINE) 
        return FALSE; 
 
    return TRUE; 
} 
 
// ;end_internal 
 
//------------------------------------------------------------------------ 
// 
//  Method:    SecurityCheckDataURL(pszURL) 
// 
//  Synopsis:  Check that the data URL is within the same security zone 
//             as the document that loaded the control. 
// 
//  Arguments: URL to check 
// 
//  Returns:   S_OK upon success. 
//             E_INVALID if the security check failed or we failed to get 
//               an interface that we needed 
// 
//------------------------------------------------------------------------ 
 
 
// ;begin_internal 
// Wendy Richards(v-wendri) 6/6/97 
// Copied this here because I couldn't link without it. The version 
// of URLMON.LIB I have does not have this symbol exported 
// ;end_internal 
 
const IID IID_IInternetHostSecurityManager = {0x3af280b6,0xcb3f,0x11d0,{0x89,0x1e,0x00,0xc0,0x4f,0xb6,0xbf,0xc4}}; 
 
#define MAX_SEC_ID 256 
 
STDMETHODIMP CTDCCtl::SecurityCheckDataURL(LPOLESTR pszURL) 
{ 
    CComQIPtr<IServiceProvider, &IID_IServiceProvider> pSP(m_spClientSite); 
    CComPtr<IInternetSecurityManager> pSM; 
    CComPtr<IInternetHostSecurityManager> pHSM; 
    CComPtr<IMoniker> pMoniker; 
 
    BYTE     bSecIDHost[MAX_SEC_ID], bSecIDURL[MAX_SEC_ID]; 
    DWORD    cbSecIDHost = MAX_SEC_ID, cbSecIDURL = MAX_SEC_ID;  
    HRESULT  hr = E_FAIL; 
 
    USES_CONVERSION; 
 
    // If we're running under the timer, it's quite possible our ClientSite will 
    // disappear out from under us.  We'll obviously fail the security check, 
    // but things are shutting down anyway.. 
    if (pSP==NULL) 
        goto Cleanup; 
 
    hr = CoInternetCreateSecurityManager(pSP, &pSM, 0L); 
    if (!SUCCEEDED(hr)) 
        goto Cleanup; 
 
    hr = pSP->QueryService(IID_IInternetHostSecurityManager, 
                           IID_IInternetHostSecurityManager, 
                           (LPVOID *)&pHSM); 
    if (!SUCCEEDED(hr)) 
        goto Cleanup; 
 
    hr = pHSM->GetSecurityId(bSecIDHost, &cbSecIDHost, 0L); 
    if (!SUCCEEDED(hr)) 
        goto Cleanup; 
 
    hr = pSM->GetSecurityId(OLE2W(pszURL), bSecIDURL, &cbSecIDURL, 0L); 
    if (!SUCCEEDED(hr)) 
        goto Cleanup; 
 
    if (cbSecIDHost != cbSecIDURL) 
    { 
        hr = E_FAIL; 
        goto Cleanup; 
    }    
 
    if (memcmp(bSecIDHost, bSecIDURL, cbSecIDHost) != 0) 
    { 
        hr = E_FAIL; 
        goto Cleanup; 
    } 
 
Cleanup: 
#ifdef ATLTRACE 
    LPOLESTR pszHostName = NULL; 
    TCHAR *pszFailPass = hr ? _T("Failed") : _T("Passed"); 
    GetHostURL(m_spClientSite, &pszHostName); 
    ATLTRACE(_T("CTDCCtl: %s security check on %S referencing %S\n"), pszFailPass, 
             pszHostName, pszURL); 
    bSecIDHost[cbSecIDHost] = 0; 
    bSecIDURL[cbSecIDURL] = 0; 
    ATLTRACE(_T("CTDCCtl: Security ID Host %d bytes: %s\n"), cbSecIDHost, bSecIDHost); 
    ATLTRACE(_T("CTDCCtl: Security ID URL %d bytes: %s\n"), cbSecIDURL, bSecIDURL); 
    CoTaskMemFree(pszHostName); 
#endif 
    return hr; 
} 
 
//------------------------------------------------------------------------ 
// 
//  Method:    OnData() 
// 
//  Synopsis:  Accepts a chunk of data loaded from a URL and parses it. 
// 
//  Arguments: pBSC       The invoking data transfer object. 
//             pBytes     Character buffer containing data. 
//             dwSize     Count of the number of bytes in 'pBytes'. 
// 
//  Returns:   Nothing. 
// 
//------------------------------------------------------------------------ 
 
void CTDCCtl::OnData(CMyBindStatusCallback<CTDCCtl> *pBSC, BYTE *pBytes, DWORD dwSize) 
{ 
    HRESULT hr; 
    CTDCUnify::ALLOWDOMAINLIST nAllowDomainList; 
 
    if (pBSC != m_pBSC) 
    { 
        OutputDebugStringX(_T("OnData called from invalid callback object\n")); 
        hr = S_OK; 
        goto Cleanup; 
    } 
 
 
    if (pBytes != NULL && dwSize != 0) 
    { 
        OutputDebugStringX(_T("OnData called with data buffer\n")); 
        //  Process this chunk of data  
        // 
        hr = m_pUnify->ConvertByteBuffer(pBytes, dwSize); 
 
        if (!m_fSecurityChecked) 
        { 
            // Note that we MUST check for the allow domain list at the 
            // front of every file, even if it's on the same host.  This 
            // is to make sure if we always strip off the @!allow_domain line. 
            nAllowDomainList = m_pUnify->CheckForAllowDomainList(); 
 
            switch (nAllowDomainList) 
            { 
                // Don't have enough chars to tell yet. 
                case CTDCUnify::ALLOW_DOMAINLIST_DONTKNOW: 
                    // Return without errors or arborting. 
                    // Presumably the next data packet will bring more info. 
                    return; 
 
                case CTDCUnify::ALLOW_DOMAINLIST_NO: 
                    hr = E_FAIL; 
                    // It's not on the exception list, still try to see if 
                    // it's in the same domain. 
                    break; 
 
                case CTDCUnify::ALLOW_DOMAINLIST_YES: 
                    // The file is decorated.  Now check the domain list 
                    // against our host domain name. 
                    hr = SecurityMatchAllowDomainList(); 
#ifdef ATLTRACE 
                    if (!hr) ATLTRACE(_T("CTDCCtl: @!allow_domain list matched.")); 
                    else ATLTRACE(_T("CTDCCtl: @!allow_domain list did not match")); 
#endif 
                    break; 
            } 
 
            // Unless we passed the previous security check, we still have to 
            // do the next one. 
            if (FAILED(hr)) 
            { 
                if (FAILED(hr = SecurityCheckDataURL(m_pBSC->m_pszURL))) 
                    goto Error; 
            } 
            else 
            { 
                hr = SecurityMatchProtocols(m_pBSC->m_pszURL); 
                if (FAILED(hr)) 
                    goto Error; 
            } 
 
 
            // Set m_fSecurityChecked only if it passes security.  This is in case for some 
            // reason we get more callbacks before the StopTransfer takes affect. 
            m_fSecurityChecked = TRUE;             
        } 
 
        // Normal case, we can process data! 
        hr = m_pUnify->AddWcharBuffer(FALSE); 
 
    } 
    else if (pBytes == NULL || dwSize == 0) 
    { 
        OutputDebugStringX(_T("OnData called with empty (terminating) buffer\n")); 
 
        // We might not have gotten enough to check security yet. 
        if (!m_fSecurityChecked) 
        { 
            if (FAILED(hr = SecurityCheckDataURL(m_pBSC->m_pszURL))) 
                goto Error; 
        } 
 
        //  No more data - trigger an EOF 
        // 
        hr = m_pUnify->AddWcharBuffer(TRUE); // last chance to parse any stragglers 
 
        if (m_pArr!=NULL) 
            hr = m_pArr->EOF(); 
 
        TerminateDataLoad(); 
    } 
 
Cleanup: 
    //  Void fn - can't return an error code ... 
    // 
    if (SUCCEEDED(m_hrDownloadStatus)) 
        m_hrDownloadStatus = hr; 
    return; 
 
Error: 
    // Security failure. 
    // Abort the current download 
    if (m_pBSC && m_pBSC->m_spBinding) 
    { 
        (void) m_pBSC->m_spBinding->Abort(); 
    } 
 
    m_hrDownloadStatus = hr; 
 
    // Notify data set changed for the abort 
    if (m_pEventBroker != NULL) 
        hr = m_pEventBroker->STDDataSetChanged(); 
    goto Cleanup; 
} 
 
// 
// Utility routine to get our  
// 
HRESULT 
GetHostURL(IOleClientSite *pSite, LPOLESTR *ppszHostName) 
{ 
    HRESULT hr; 
    CComPtr<IMoniker> spMoniker; 
    CComPtr<IBindCtx> spBindCtx; 
 
    if (!pSite) 
    { 
        hr = E_FAIL; 
        goto Cleanup; 
    } 
 
    hr = pSite->GetMoniker(OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_CONTAINER, 
                           &spMoniker); 
    if (FAILED(hr)) 
        goto Cleanup; 
 
    hr = CreateBindCtx(0, &spBindCtx); 
    if (FAILED(hr)) 
        goto Cleanup; 
 
    hr = spMoniker->GetDisplayName(spBindCtx, NULL, ppszHostName); 
    if (FAILED(hr)) 
        goto Cleanup; 
 
Cleanup: 
    return hr; 
} 
 
HRESULT 
CTDCCtl::SecurityMatchProtocols(LPOLESTR pszURL) 
{ 
    HRESULT hr = E_FAIL; 
 
    LPOLESTR pszHostURL = NULL; 
    LPWCH pszPostHostProtocol; 
    LPWCH pszPostProtocol; 
 
    if (FAILED(GetHostURL(m_spClientSite, &pszHostURL))) 
        goto Cleanup; 
 
    pszPostHostProtocol = wch_chr(pszHostURL, _T(':')); 
    pszPostProtocol     = wch_chr(pszURL, _T(':')); 
    if (!pszPostHostProtocol || !pszPostProtocol) 
        goto Cleanup; 
    else 
    { 
        int ccChars1 = pszPostHostProtocol - pszHostURL; 
        int ccChars2 = pszPostProtocol - pszURL; 
        if (ccChars1 != ccChars2) 
            goto Cleanup; 
        else if (wch_ncmp(pszHostURL, pszURL, ccChars1) != 0) 
            goto Cleanup; 
    } 
    hr = S_OK; 
 
Cleanup: 
    if (pszHostURL) 
        CoTaskMemFree(pszHostURL); 
     
    return hr; 
} 
 
HRESULT 
CTDCCtl::SecurityMatchAllowDomainList() 
{ 
    HRESULT hr; 
    WCHAR swzHostDomain[INTERNET_MAX_HOST_NAME_LENGTH]; 
    DWORD cchHostDomain = INTERNET_MAX_HOST_NAME_LENGTH; 
    LPOLESTR pszHostName = NULL; 
 
    hr = GetHostURL(m_spClientSite, &pszHostName); 
    if (FAILED(hr)) 
        goto Cleanup; 
 
    hr = CoInternetParseUrl(pszHostName, PARSE_DOMAIN, 0, swzHostDomain, cchHostDomain, 
                            &cchHostDomain, 0); 
    if (FAILED(hr)) 
        goto Cleanup; 
 
    hr = m_pUnify->MatchAllowDomainList(swzHostDomain); 
 
Cleanup: 
    CoTaskMemFree(pszHostName); 
    return hr; 
}