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;
}