|
|
//------------------------------------------------------------------------
//
// Tabular Data Control
// Copyright (C) Microsoft Corporation, 1996, 1997
//
// File: TDCCtl.cpp
//
// Contents: Implementation of the CTDCCtl ActiveX control.
//
//------------------------------------------------------------------------
#include "stdafx.h"
#include <simpdata.h>
#include "TDCIds.h"
#include "TDC.h"
#include <MLang.h>
#include "Notify.h"
#include "TDCParse.h"
#include "TDCArr.h"
#include "TDCCtl.h"
#include "locale.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(); }
// For some reason the standard definition of VARIANT_TRUE (0xffff) generates
// truncation warnings when assigned to a VARIANT_BOOL
#define TDCVARIANT_TRUE -1
//------------------------------------------------------------------------
//
// 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 = FALSE; m_fSortAscending = TRUE; m_fAppendData = 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 = 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 = (VARIANT_BOOL)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 ? TDCVARIANT_TRUE : VARIANT_FALSE; return S_OK; }
STDMETHODIMP CTDCCtl::put_SortAscending(VARIANT_BOOL fSortAscending) { m_fSortAscending = fSortAscending ? TRUE : FALSE; 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;
*pbstrCharSet = NULL;
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.uiInternetEncoding; } } return S_OK; }
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 S_OK; }
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 ? TDCVARIANT_TRUE : VARIANT_FALSE; return S_OK; }
STDMETHODIMP CTDCCtl::put_AppendData(VARIANT_BOOL fAppendData) { m_fAppendData = fAppendData ? TRUE : FALSE; return S_OK; }
STDMETHODIMP CTDCCtl::get_CaseSensitive(VARIANT_BOOL* pfCaseSensitive) { *pfCaseSensitive = m_fCaseSensitive ? TDCVARIANT_TRUE : VARIANT_FALSE; return S_OK; }
STDMETHODIMP CTDCCtl::put_CaseSensitive(VARIANT_BOOL fCaseSensitive) { m_fCaseSensitive = fCaseSensitive ? TRUE : FALSE; 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; m_pSTD->AddRef(); } 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;
// Find out our Ambient Charset. We need to know this surprisingly
// early in life.
VARIANT varCodepage; // 0 means user didn't set one, so ask our container.
VariantInit(&varCodepage); GetAmbientProperty(DISPID_AMBIENT_CODEPAGE, varCodepage);
// Ultimate default is Latin-1
m_nAmbientCodePage = (varCodepage.vt == VT_UI4) ? (ULONG)varCodepage.lVal : CP_1252;
// ignore Unicode ambient codepage - we want to allow non-Unicode
// data files from Unicode pages. If the data file is Unicode,
// we'll find out anyway when we see the Unicode signature.
if (m_nAmbientCodePage == UNICODE_CP || m_nAmbientCodePage == UNICODE_REVERSE_CP) { m_nAmbientCodePage = CP_1252; }
// 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(m_pBSC);
// 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(CMyBindStatusCallback<CTDCCtl> *pBSC) { HRESULT hr = S_OK;
// if the termination isn't for the current download, ignore it (bug 104042)
if (pBSC != m_pBSC) goto done;
// 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;
done: 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];
//
// Default LCID
//
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_nAmbientCodePage, 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(m_pBSC); if (m_pEventBroker) { // Fire data set changed to indicate query failed,
m_pEventBroker->STDDataSetChanged(); // and go complete.
UpdateReadyState(READYSTATE_COMPLETE); } goto Cleanup; }
//------------------------------------------------------------------------
//
// 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
EXTERN_C const IID IID_IInternetHostSecurityManager;
#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 = S_OK; CTDCUnify::ALLOWDOMAINLIST nAllowDomainList;
if (pBSC != m_pBSC) { OutputDebugStringX(_T("OnData called from invalid callback object\n")); goto Cleanup; }
// ignore callbacks from an aborted download
if (m_hrDownloadStatus == E_ABORT) goto Cleanup;
// Process this chunk of data
//
hr = m_pUnify->ConvertByteBuffer(pBytes, dwSize);
if (hr == S_FALSE) { // not enough data has shown up yet, just keep going
hr = S_OK; goto Cleanup; }
if (hr) goto Error;
if (!m_fSecurityChecked) { // this forces the code below to check the DataURL, unless the allow_domain
// list needs checking and it passes. In that case, we need only check
// the protocols, not the whole URL.
hr = E_FAIL;
if (!m_pUnify->ProcessedAllowDomainList()) { // 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: if (pBytes != NULL && dwSize != 0) { // Return without errors or arborting.
// Presumably the next data packet will bring more info.
return; } _ASSERT(FAILED(hr)); break;
case CTDCUnify::ALLOW_DOMAINLIST_NO: _ASSERT(FAILED(hr)); 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; }
if (pBytes != NULL && dwSize != 0) { OutputDebugStringX(_T("OnData called with data buffer\n"));
// Normal case, we can process data!
hr = m_pUnify->AddWcharBuffer(FALSE); if (hr == E_ABORT) goto Error; } else if (pBytes == NULL || dwSize == 0) { OutputDebugStringX(_T("OnData called with empty (terminating) buffer\n"));
// No more data - trigger an EOF
//
hr = m_pUnify->AddWcharBuffer(TRUE); // last chance to parse any stragglers
if (hr == E_ABORT) goto Error;
if (m_pArr!=NULL) hr = m_pArr->EOF();
TerminateDataLoad(pBSC); }
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();
// also delete the downloaded file from the internet cache
m_pBSC->DeleteDataFile(); }
m_hrDownloadStatus = hr;
// Notify data set changed for the abort
if (m_pEventBroker != NULL) { hr = m_pEventBroker->STDDataSetChanged(); // and go complete.
UpdateReadyState(READYSTATE_COMPLETE); } 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; }
|