#include "private.h" #include "subsmgrp.h" #include "downld.h" #include "chanmgr.h" #include "chanmgrp.h" #include "helper.h" #include "shguidp.h" // IID_IChannelMgrPriv #undef TF_THISMODULE #define TF_THISMODULE TF_CDFAGENT const int MINUTES_PER_DAY = 24 * 60; //============================================================================== // Convert an XML OM to a TaskTrigger by looking for and converting //============================================================================== // returns S_OK if succeeded. (S_FALSE if succeeded but TASK_TRIGGER was truncated). // returns E_FAIL if no task trigger retrieved (returned *ptt is invalid TASK_TRIGGER) // You Must fill in ptt->cbTriggerSize!!! // User can pass in Schedule element itself, or any parent element, in pRootEle HRESULT XMLScheduleElementToTaskTrigger(IXMLElement *pRootEle, TASK_TRIGGER *ptt) { HRESULT hr = E_FAIL; if (!pRootEle || !ptt) return E_INVALIDARG; ASSERT(ptt->cbTriggerSize == sizeof(TASK_TRIGGER)); CExtractSchedule *pSched = new CExtractSchedule(pRootEle, NULL); if (pSched) { if (SUCCEEDED(pSched->Run())) { hr = pSched->GetTaskTrigger(ptt); } delete pSched; } return hr; } // CExtractSchedule doesn't get used during channel update // It's just used to traverse the OM and find the first Schedule tag, to // parse out the schedule info CExtractSchedule::CExtractSchedule(IXMLElement *pEle, CExtractSchedule *pExtractRoot) : CProcessElement(NULL, NULL, pEle) { m_pExtractRoot = pExtractRoot; if (!pExtractRoot) m_pExtractRoot = this; } HRESULT CExtractSchedule::Run() { // Allow user to pass in Schedule element itself, or Root element BSTR bstrItem=NULL; HRESULT hr; m_pElement->get_tagName(&bstrItem); if (bstrItem && *bstrItem && !StrCmpIW(bstrItem, L"Schedule")) { hr = ProcessItemInEnum(bstrItem, m_pElement); } else { hr = CProcessElement::Run(); } SysFreeString(bstrItem); return hr; } HRESULT CExtractSchedule::ProcessItemInEnum(LPCWSTR pwszTagName, IXMLElement *pItem) { if (!StrCmpIW(pwszTagName, L"Schedule")) { CProcessSchedule *pPS = new CProcessSchedule(this, NULL, pItem); if (pPS) { pPS->Run(); if (pPS->m_tt.cbTriggerSize) { ASSERT(pPS->m_tt.cbTriggerSize == sizeof(m_tt)); m_pExtractRoot->m_tt = pPS->m_tt; } delete pPS; } return E_ABORT; // abort our enumerations } else if (!StrCmpIW(pwszTagName, L"Channel")) { return DoChild(new CExtractSchedule(pItem, m_pExtractRoot)); } return S_OK; // ignore other tags } HRESULT CExtractSchedule::GetTaskTrigger(TASK_TRIGGER *ptt) { if ((0 == m_tt.cbTriggerSize) || // No task trigger (0 == m_tt.wBeginYear)) // Invalid task trigger { return E_FAIL; } if (m_tt.cbTriggerSize <= ptt->cbTriggerSize) { *ptt = m_tt; return S_OK; } WORD cbTriggerSize = ptt->cbTriggerSize; CopyMemory(ptt, &m_tt, cbTriggerSize); ptt->cbTriggerSize = cbTriggerSize; return S_FALSE; } //============================================================================== // XML OM Helper functions //============================================================================== HRESULT GetXMLAttribute(IXMLElement *pItem, LPCWSTR pwszAttribute, VARIANT *pvRet) { BSTR bstrName=NULL; HRESULT hr=E_FAIL; pvRet->vt = VT_EMPTY; bstrName = SysAllocString(pwszAttribute); if (bstrName && SUCCEEDED(pItem->getAttribute(bstrName, pvRet))) { hr = S_OK; } SysFreeString(bstrName); return hr; } HRESULT GetXMLStringAttribute(IXMLElement *pItem, LPCWSTR pwszAttribute, BSTR *pbstrRet) { VARIANT var; BSTR bstrName=NULL; HRESULT hr=E_FAIL; *pbstrRet = NULL; var.vt = VT_EMPTY; bstrName = SysAllocString(pwszAttribute); if (bstrName && SUCCEEDED(pItem->getAttribute(bstrName, &var))) { if (var.vt == VT_BSTR && var.bstrVal != NULL) { *pbstrRet = var.bstrVal; hr = S_OK; } } SysFreeString(bstrName); if (FAILED(hr) && var.vt != VT_EMPTY) VariantClear(&var); return hr; } DWORD GetXMLDwordAttribute(IXMLElement *pItem, LPCWSTR pwszAttribute, DWORD dwDefault) { VARIANT var; if (SUCCEEDED(GetXMLAttribute(pItem, pwszAttribute, &var))) { if (var.vt == VT_I4) return var.lVal; if (var.vt == VT_I2) return var.iVal; if (var.vt == VT_BSTR) { LPCWSTR pwsz = var.bstrVal; DWORD dwRet; if (!StrToIntExW(pwsz, 0, (int *)&dwRet)) dwRet = dwDefault; SysFreeString(var.bstrVal); return dwRet; } VariantClear(&var); } return dwDefault; } // If failure return code, *pfRet wasn't changed HRESULT GetXMLBoolAttribute(IXMLElement *pItem, LPCWSTR pwszAttribute, BOOL *pfRet) { VARIANT var; HRESULT hr=E_FAIL; if (SUCCEEDED(GetXMLAttribute(pItem, pwszAttribute, &var))) { if (var.vt == VT_BOOL) { *pfRet = (var.boolVal == VARIANT_TRUE); hr = S_OK; } else if (var.vt == VT_BSTR) { if (!StrCmpIW(var.bstrVal, L"YES") || !StrCmpIW(var.bstrVal, L"\"YES\"")) { *pfRet = TRUE; hr = S_OK; } else if (!StrCmpIW(var.bstrVal, L"NO") || !StrCmpIW(var.bstrVal, L"\"NO\"")) { *pfRet = FALSE; hr = S_OK; } } else hr = E_FAIL; VariantClear(&var); } return hr; } HRESULT GetXMLTimeAttributes(IXMLElement *pItem, CDF_TIME *pTime) { pTime->wReserved = 0; pTime->wDay = (WORD) GetXMLDwordAttribute(pItem, L"DAY", 0); pTime->wHour = (WORD) GetXMLDwordAttribute(pItem, L"HOUR", 0); pTime->wMin = (WORD) GetXMLDwordAttribute(pItem, L"MIN", 0); pTime->dwConvertedMinutes = (24 * 60 * pTime->wDay) + ( 60 * pTime->wHour) + ( pTime->wMin); return S_OK; } inline BOOL IsNumber(WCHAR x) { return (x >= L'0' && x <= L'9'); } HRESULT GetXMLTimeZoneAttribute(IXMLElement *pItem, LPCWSTR pwszAttribute, int *piRet) { BSTR bstrVal; HRESULT hrRet = E_FAIL; ASSERT(pItem && piRet); if (SUCCEEDED(GetXMLStringAttribute(pItem, pwszAttribute, &bstrVal))) { if(bstrVal && bstrVal[0] && IsNumber(bstrVal[1]) && IsNumber(bstrVal[2]) && IsNumber(bstrVal[3]) && IsNumber(bstrVal[4])) { *piRet = 1000*(bstrVal[1] - L'0') + 100*(bstrVal[2] - L'0') + 10*(bstrVal[3] - L'0') + bstrVal[4] - L'0'; hrRet = S_OK; } if(bstrVal[0] == L'-') *piRet *= -1; } SysFreeString(bstrVal); return hrRet; } //============================================================================== // TEMP fn to convert ISO 1234:5678 to SYSTEMTIME // ISODateToSystemTime returns false if there is a parse error // true if there isn't //============================================================================== BOOL ValidateSystemTime(SYSTEMTIME *time) { // IE6 27665. Some incompetent XML file writers provide slightly invalid dates, like 9-31-01. // Everybody knows that September has 30 days. Anyway, we'll do some minimal fix-up here. switch (time->wMonth) { case 2: // February // Rule is, every four years is a leap year, except for centuries. if ((time->wYear % 4) || ((time->wYear % 100)==0)) { if (time->wDay > 28) { time->wDay = 28; } } else if (time->wDay > 29) { time->wDay = 29; } break; case 1: // January case 3: // March case 5: // May case 7: // July case 8: // August case 10: // October case 12: // December if (time->wDay>31) { time->wDay = 31; } break; default: // the other 4 months. These have 30 days, apparently. if (time->wDay>30) { time->wDay = 30; } break; } return TRUE; } // yyyy-mm-dd[Thh:mm[+zzzz]] BOOL ISODateToSystemTime(LPCWSTR string, SYSTEMTIME *time, long *timezone) { if (!string || (lstrlenW(string) < 10) || !time) return FALSE; ZeroMemory(time, sizeof(SYSTEMTIME)); if (timezone) *timezone = 0; if (IsNumber(string[0]) && IsNumber(string[1]) && IsNumber(string[2]) && IsNumber(string[3]) && (string[4] != L'\0') && IsNumber(string[5]) && IsNumber(string[6]) && (string[7] != L'\0') && IsNumber(string[8]) && IsNumber(string[9])) { time->wYear = 1000*(string[0] - L'0') + 100*(string[1] - L'0') + 10*(string[2] - L'0') + string[3] - L'0'; time->wMonth = 10*(string[5] - L'0') + string[6] - L'0'; time->wDay = 10*(string[8] - L'0') + string[9] - L'0'; } else { return FALSE; } if ((string[10]!= L'\0') && IsNumber(string[11]) && IsNumber(string[12]) && (string[13] != L'\0') && IsNumber(string[14]) && IsNumber(string[15])) { time->wHour = 10*(string[11] - L'0') + string[12] - L'0'; time->wMinute = 10*(string[14] - L'0') + string[15] - L'0'; if (timezone && (string[16]!= L'\0') && IsNumber(string[17]) && IsNumber(string[18]) && IsNumber(string[19]) && IsNumber(string[20])) { *timezone = 1000*(string[17] - L'0') + 100*(string[18] - L'0') + 10*(string[19] - L'0') + string[20] - L'0'; if(string[16] == L'-') *timezone = - *timezone; } } return ValidateSystemTime(time); } //============================================================================== // CProcessElement class provides generic support for sync or async enumeration // of an XML OM //============================================================================== CProcessElement::CProcessElement(CProcessElementSink *pParent, CProcessRoot *pRoot, IXMLElement *pEle) { ASSERT(m_pRunAgent == NULL && m_pCurChild == NULL && m_pCollection == NULL); m_pElement = pEle; pEle->AddRef(); m_pRoot = pRoot; m_pParent = pParent; } CProcessElement::~CProcessElement() { ASSERT(!m_pCurChild); CRunDeliveryAgent::SafeRelease(m_pRunAgent); SAFERELEASE(m_pCollection); SAFERELEASE(m_pElement); SAFERELEASE(m_pChildElement); } HRESULT CProcessElement::Pause(DWORD dwFlags) { if (m_pCurChild) return m_pCurChild->Pause(dwFlags); ASSERT(m_pRunAgent); if (m_pRunAgent) return m_pRunAgent->AgentPause(dwFlags); return E_FAIL; } HRESULT CProcessElement::Resume(DWORD dwFlags) { if (m_pCurChild) return m_pCurChild->Resume(dwFlags); if (m_pRunAgent) m_pRunAgent->AgentResume(dwFlags); else DoEnumeration(); return S_OK; } HRESULT CProcessElement::Abort(DWORD dwFlags) { if (m_pCurChild) { m_pCurChild->Abort(dwFlags); SAFEDELETE(m_pCurChild); } if (m_pRunAgent) { // Prevent reentrancy into OnAgentEnd m_pRunAgent->LeaveMeAlone(); m_pRunAgent->AgentAbort(dwFlags); CRunDeliveryAgent::SafeRelease(m_pRunAgent); } return S_OK; } HRESULT CProcessElement::Run() { ASSERT(!m_pCollection); ASSERT(m_lMax == 0); // ASSERT(m_fSentEnumerationComplete == FALSE); // DoEnumeration may have sent this m_lIndex = 0; if (SUCCEEDED(m_pElement->get_children(&m_pCollection)) && m_pCollection) { m_pCollection->get_length(&m_lMax); } else m_lMax = 0; return DoEnumeration(); // Will call OnChildDone when appropriate } HRESULT CProcessElement::OnAgentEnd(const SUBSCRIPTIONCOOKIE *pSubscriptionCookie, long lSizeDownloaded, HRESULT hrResult, LPCWSTR wszResult, BOOL fSynchronous) { // Our delivery agent is done. Continue enumeration ASSERT(!m_pCurChild); if (lSizeDownloaded > 0) m_pRoot->m_dwCurSizeKB += (ULONG) lSizeDownloaded; TraceMsg(TF_THISMODULE, "ChannelAgent up to %dkb of %dkb", m_pRoot->m_dwCurSizeKB, m_pRoot->m_pChannelAgent->m_dwMaxSizeKB); if ((hrResult == INET_E_AGENT_MAX_SIZE_EXCEEDED) || (hrResult == INET_E_AGENT_CACHE_SIZE_EXCEEDED)) { DBG("CProcessElement got max size or cache size exceeded; not running any more delivery agents"); m_pRoot->m_fMaxSizeExceeded = TRUE; m_pRoot->m_pChannelAgent->SetEndStatus(hrResult); } CRunDeliveryAgent::SafeRelease(m_pRunAgent); if (fSynchronous) { // we are still in our DoDeliveryAgent call. Let it return out through there. return S_OK; } // Continue enumeration, or start enumeration if we haven't yet. if (m_fStartedEnumeration) DoEnumeration(); else Run(); return S_OK; } HRESULT CProcessElement::DoEnumeration() { IDispatch *pDisp; IXMLElement *pItem; BSTR bstrTagName; VARIANT vIndex, vEmpty; HRESULT hr = S_OK; BOOL fStarted = FALSE; m_fStartedEnumeration = TRUE; ASSERT(m_pCollection || !m_lMax); if (m_pRoot && m_pRoot->IsPaused()) { DBG("CProcessElement::DoEnumeration returning E_PENDING, we're paused"); return E_PENDING; } vEmpty.vt = VT_EMPTY; for (; (m_lIndex < m_lMax) && !fStarted && (hr != E_ABORT); m_lIndex++) { vIndex.vt = VT_UI4; vIndex.lVal = m_lIndex; if (SUCCEEDED(m_pCollection->item(vIndex, vEmpty, &pDisp))) { if (SUCCEEDED(pDisp->QueryInterface(IID_IXMLElement, (void **)&pItem))) { if (SUCCEEDED(pItem->get_tagName(&bstrTagName)) && bstrTagName) { SAFERELEASE(m_pChildElement); m_pChildElement=pItem; m_pChildElement->AddRef(); hr = ProcessItemInEnum(bstrTagName, pItem); SysFreeString(bstrTagName); if (hr == E_PENDING) fStarted = TRUE; } pItem->Release(); } pDisp->Release(); } } // Tell this instance we're done with enumeration, unless we already have if (!fStarted && !m_fSentEnumerationComplete) { m_fSentEnumerationComplete = TRUE; hr = EnumerationComplete(); // WARNING this eats E_ABORT if (hr == E_PENDING) fStarted = TRUE; } // Notify our parent if we're done with our enumeration, if (!fStarted) { if (m_pParent) // Check for CExtractSchedule m_pParent->OnChildDone(this, hr); // This may delete us } if (hr == E_ABORT) return E_ABORT; return (fStarted) ? E_PENDING : S_OK; } HRESULT CProcessElement::OnChildDone(CProcessElement *pChild, HRESULT hr) { ASSERT(pChild && (!m_pCurChild || (pChild == m_pCurChild))); if (m_pCurChild) { // A child returned from async operation. SAFEDELETE(m_pCurChild); // Continue enumeration. This will call our parent's ChildDone if it // finishes, so it may delete us. DoEnumeration(); } else { // Our child has finished synchronously. Ignore (DoChild() will take care of it). } return S_OK; } HRESULT CProcessElement::DoChild(CProcessElement *pChild) { HRESULT hr; ASSERT(m_pCurChild == NULL); if (!pChild) return E_POINTER; // FEATURE should call parent's OnChildDone here hr = pChild->Run(); if (hr == E_PENDING) { // Returned async. Will call back OnChildDone. m_pCurChild = pChild; return E_PENDING; } // Synchronously returned. Clean up. delete pChild; return hr; } // E_PENDING if async operation started HRESULT CProcessElement::DoDeliveryAgent(ISubscriptionItem *pItem, REFCLSID rclsid, LPCWSTR pwszURL) { ASSERT(pItem); HRESULT hr=E_FAIL; if (m_pRoot->m_fMaxSizeExceeded) { // DBG("CProcessElement::RunDeliveryAgent failing; exceeded max size."); return E_FAIL; } if (m_pRunAgent) { DBG_WARN("CProcessElement::DoDeliveryAgent already running!"); return E_FAIL; } m_pRunAgent = new CChannelAgentHolder(m_pRoot->m_pChannelAgent, this); if (m_pRunAgent) { hr = m_pRunAgent->Init(this, pItem, rclsid); if (SUCCEEDED(hr)) hr = m_pRunAgent->StartAgent(); if (hr == E_PENDING) { m_pRoot->m_pChannelAgent->SendUpdateProgress(pwszURL, ++(m_pRoot->m_iTotalStarted), -1, m_pRoot->m_dwCurSizeKB); } else CRunDeliveryAgent::SafeRelease(m_pRunAgent); } else hr = E_OUTOFMEMORY; return hr; } HRESULT CProcessElement::DoSoftDist(IXMLElement *pItem) { HRESULT hr = S_OK; ISubscriptionItem *pSubsItem; if (SUCCEEDED(m_pRoot->CreateStartItem(&pSubsItem))) { if (pSubsItem) { hr = DoDeliveryAgent(pSubsItem, CLSID_CDLAgent); pSubsItem->Release(); } } return hr; } HRESULT CProcessElement::DoWebCrawl(IXMLElement *pItem, LPCWSTR pwszURL /* = NULL */) { BSTR bstrURL=NULL, bstrTmp=NULL; HRESULT hr = S_OK; ISubscriptionItem *pSubsItem; DWORD dwLevels=0, dwFlags; LPWSTR pwszUrl2=NULL; BOOL fOffline=FALSE; if (!pwszURL && SUCCEEDED(GetXMLStringAttribute(pItem, L"HREF", &bstrURL)) && bstrURL) pwszURL = bstrURL; if (pwszURL) { SYSTEMTIME stLastMod; long lTimezone; hr = CombineWithBaseUrl(pwszURL, &pwszUrl2); if (SUCCEEDED(hr) && pwszUrl2) pwszURL = pwszUrl2; // Got a new URL hr = CUrlDownload::IsValidURL(pwszURL); if (SUCCEEDED(hr) && SUCCEEDED(GetXMLStringAttribute(m_pElement, L"LastMod", &bstrTmp)) && ISODateToSystemTime(bstrTmp, &stLastMod, &lTimezone)) { // Check Last Modified time TCHAR szThisUrl[INTERNET_MAX_URL_LENGTH]; char chBuf[MY_MAX_CACHE_ENTRY_INFO]; DWORD dwBufSize = sizeof(chBuf); LPINTERNET_CACHE_ENTRY_INFO lpInfo = (LPINTERNET_CACHE_ENTRY_INFO) chBuf; MyOleStrToStrN(szThisUrl, INTERNET_MAX_URL_LENGTH, pwszURL); hr = GetUrlInfoAndMakeSticky(NULL, szThisUrl, lpInfo, dwBufSize, 0); if (SUCCEEDED(hr)) { FILETIME ft; if (SystemTimeToFileTime(&stLastMod, &ft)) { // APPCOMPAT: In an ideal world, all servers would support LastModifiedTime accurately. // In our world, some do not support it and wininet returns a value of zero. // Without maintaining checksums of the files, we have two options: always download // the URL or never update it. Since it would be odd not to update it, we always do. if ((lpInfo->LastModifiedTime.dwHighDateTime || lpInfo->LastModifiedTime.dwLowDateTime) && (lpInfo->LastModifiedTime.dwHighDateTime >= ft.dwHighDateTime) && ((lpInfo->LastModifiedTime.dwHighDateTime > ft.dwHighDateTime) || (lpInfo->LastModifiedTime.dwLowDateTime >= ft.dwLowDateTime))) { // Skip it. TraceMsg(TF_THISMODULE, "Running webcrawl OFFLINE due to Last Modified time URL=%ws", pwszURL); fOffline = TRUE; } } } hr = S_OK; } SAFEFREEBSTR(bstrTmp); if (SUCCEEDED(hr) && SUCCEEDED(m_pRoot->CreateStartItem(&pSubsItem))) { WriteOLESTR(pSubsItem, c_szPropURL, pwszURL); dwLevels = GetXMLDwordAttribute(pItem, L"LEVEL", 0); if (dwLevels && m_pRoot->IsChannelFlagSet(CHANNEL_AGENT_PRECACHE_SOME)) { // Note: MaxChannelLevels is stored as N+1 because 0 // means the restriction is disabled. DWORD dwMaxLevels = SHRestricted2W(REST_MaxChannelLevels, NULL, 0); if (!dwMaxLevels) dwMaxLevels = MAX_CDF_CRAWL_LEVELS + 1; if (dwLevels >= dwMaxLevels) dwLevels = dwMaxLevels - 1; WriteDWORD(pSubsItem, c_szPropCrawlLevels, dwLevels); } if (fOffline) { if (SUCCEEDED(ReadDWORD(pSubsItem, c_szPropCrawlFlags, &dwFlags))) { dwFlags |= CWebCrawler::WEBCRAWL_PRIV_OFFLINE_MODE; WriteDWORD(pSubsItem, c_szPropCrawlFlags, dwFlags); } } hr = DoDeliveryAgent(pSubsItem, CLSID_WebCrawlerAgent, pwszURL); SAFERELEASE(pSubsItem); } } if (bstrURL) SysFreeString(bstrURL); if (pwszUrl2) MemFree(pwszUrl2); return hr; } BOOL CProcessElement::ShouldDownloadLogo(IXMLElement *pLogo) { return m_pRoot->IsChannelFlagSet(CHANNEL_AGENT_PRECACHE_SOME); } // If relative url, will combine with most recent base URL // *ppwszRetUrl should be NULL & will be MemAlloced HRESULT CProcessElement::CombineWithBaseUrl(LPCWSTR pwszUrl, LPWSTR *ppwszRetUrl) { ASSERT(ppwszRetUrl && !*ppwszRetUrl && pwszUrl); // Optimization: if pwszURL is absolute, we don't need to do this expensive // combine operation // if (*pwszUrl != L'/') // BOGUS // { // *ppwszRetUrl = StrDupW(pwszUrl); // return S_FALSE; // Succeeded; pwszUrl is already OK // } // Find appropriate Base URL to use LPCWSTR pwszBaseUrl = GetBaseUrl(); WCHAR wszUrl[INTERNET_MAX_URL_LENGTH]; DWORD dwLen = ARRAYSIZE(wszUrl); if (SUCCEEDED(UrlCombineW(pwszBaseUrl, pwszUrl, wszUrl, &dwLen, 0))) { *ppwszRetUrl = StrDupW(wszUrl); return (*ppwszRetUrl) ? S_OK : E_OUTOFMEMORY; } *ppwszRetUrl = NULL; return E_FAIL; // Erg? } //============================================================================== // CProcessElement derived classes, to handle specific CDF tags //============================================================================== // CProcessRoot doesn't behave like a normal CProcessElement class. It calls // CProcessChannel to process the *same element* CProcessRoot::CProcessRoot(CChannelAgent *pParent, IXMLElement *pItem) : CProcessElement(pParent, NULL, pItem) { ASSERT(m_pDefaultStartItem == FALSE && m_pTracking == NULL && !m_dwCurSizeKB); m_pRoot = this; m_pChannelAgent = pParent; pParent->AddRef(); m_iTotalStarted = 1; } CProcessRoot::~CProcessRoot() { SAFEDELETE(m_pTracking); SAFERELEASE(m_pChannelAgent); SAFERELEASE(m_pDefaultStartItem); } // Should never get called. CProcessRoot is an odd duck. HRESULT CProcessRoot::ProcessItemInEnum(LPCWSTR pwszTagName, IXMLElement *pItem) { ASSERT(0); return E_NOTIMPL; } HRESULT CProcessRoot::CreateStartItem(ISubscriptionItem **ppItem) { if (ppItem) *ppItem = NULL; if (!m_pDefaultStartItem) { DoCloneSubscriptionItem(m_pChannelAgent->GetStartItem(), NULL, &m_pDefaultStartItem); if (m_pDefaultStartItem) { DWORD dwTemp; // Clear out properties we don't want const LPCWSTR pwszPropsToClear[] = { c_szPropCrawlLevels, c_szPropCrawlLocalDest, c_szPropCrawlActualSize, c_szPropCrawlMaxSize, c_szPropCrawlGroupID }; VARIANT varEmpty[ARRAYSIZE(pwszPropsToClear)] = {0}; ASSERT(ARRAYSIZE(pwszPropsToClear) == ARRAYSIZE(varEmpty)); m_pDefaultStartItem->WriteProperties( ARRAYSIZE(pwszPropsToClear), pwszPropsToClear, varEmpty); // Add in properties we do want dwTemp = DELIVERY_AGENT_FLAG_NO_BROADCAST | DELIVERY_AGENT_FLAG_NO_RESTRICTIONS; WriteDWORD(m_pDefaultStartItem, c_szPropAgentFlags, dwTemp); if (FAILED(ReadDWORD(m_pDefaultStartItem, c_szPropCrawlFlags, &dwTemp))) { WriteDWORD(m_pDefaultStartItem, c_szPropCrawlFlags, WEBCRAWL_GET_IMAGES|WEBCRAWL_LINKS_ELSEWHERE); } WriteLONGLONG(m_pDefaultStartItem, c_szPropCrawlNewGroupID, m_pChannelAgent->m_llCacheGroupID); } } if (m_pDefaultStartItem && ppItem) { DoCloneSubscriptionItem(m_pDefaultStartItem, NULL, ppItem); if (*ppItem) { // Add in properties for our new clone if ((m_pChannelAgent->m_dwMaxSizeKB > 0) && (m_dwCurSizeKB <= m_pChannelAgent->m_dwMaxSizeKB)) { WriteDWORD(*ppItem, c_szPropCrawlMaxSize, (m_pChannelAgent->m_dwMaxSizeKB - m_dwCurSizeKB)); } } } return (ppItem) ? (*ppItem) ? S_OK : E_FAIL : (m_pDefaultStartItem) ? S_OK : E_FAIL; } HRESULT CProcessRoot::Run() { if (FAILED(CreateStartItem(NULL))) return E_FAIL; return DoChild(new CProcessChannel(this, this, m_pElement)); } HRESULT CProcessRoot::DoTrackingFromItem(IXMLElement *pItem, LPCWSTR pwszUrl, BOOL fForceLog) { HRESULT hr = E_FAIL; // if m_pTracking is not created before this call, means no tag was found or // global logging is turned off if (m_pTracking) hr = m_pTracking->ProcessTrackingInItem(pItem, pwszUrl, fForceLog); return hr; } HRESULT CProcessRoot::DoTrackingFromLog(IXMLElement *pItem) { HRESULT hr = S_OK; if (!m_pTracking && !SHRestricted2W(REST_NoChannelLogging, m_pChannelAgent->GetUrl(), 0) && !ReadRegDWORD(HKEY_CURRENT_USER, c_szRegKey, c_szNoChannelLogging)) { m_pTracking = new CUrlTrackingCache(m_pChannelAgent->GetStartItem(), m_pChannelAgent->GetUrl()); } if (!m_pTracking) return E_OUTOFMEMORY; hr = m_pTracking->ProcessTrackingInLog(pItem); // skip tracking if PostURL is not specified if (m_pTracking->get_PostURL() == NULL) { SAFEDELETE(m_pTracking); } return hr; } // Overload this since we never do enumeration. Call delivery agent if necessary, // call m_pParent->OnChildDone if necessary HRESULT CProcessRoot::OnChildDone(CProcessElement *pChild, HRESULT hr) { ASSERT(pChild && (!m_pCurChild || (pChild == m_pCurChild))); // Our processing is done. Now we decide if we'd like to call the post agent. BSTR bstrURL=NULL; ISubscriptionItem *pStartItem; hr = S_OK; SAFEDELETE(m_pCurChild); ASSERT(m_pDefaultStartItem); ReadBSTR(m_pDefaultStartItem, c_szTrackingPostURL, &bstrURL); if (bstrURL && *bstrURL) { TraceMsg(TF_THISMODULE, "ChannelAgent calling post agent posturl=%ws", bstrURL); if (SUCCEEDED(m_pRoot->CreateStartItem(&pStartItem))) { m_pRunAgent = new CChannelAgentHolder(m_pChannelAgent, this); if (m_pRunAgent) { hr = m_pRunAgent->Init(this, pStartItem, CLSID_PostAgent); if (SUCCEEDED(hr)) hr = m_pRunAgent->StartAgent(); if (hr != E_PENDING) CRunDeliveryAgent::SafeRelease(m_pRunAgent); } pStartItem->Release(); } } SysFreeString(bstrURL); if (hr != E_PENDING) m_pParent->OnChildDone(this, hr); // This may delete us return hr; } // Our delivery agent (post agent) is done running. Tell CDF agent we're done. HRESULT CProcessRoot::OnAgentEnd(const SUBSCRIPTIONCOOKIE *pSubscriptionCookie, long lSizeDownloaded, HRESULT hrResult, LPCWSTR wszResult, BOOL fSynchronous) { if (!fSynchronous) m_pParent->OnChildDone(this, S_OK); // This may delete us return S_OK; } CProcessChannel::CProcessChannel(CProcessElementSink *pParent, CProcessRoot *pRoot, IXMLElement *pItem) : CProcessElement(pParent, pRoot, pItem) { m_fglobalLog = FALSE; } CProcessChannel::~CProcessChannel() { SAFEFREEBSTR(m_bstrBaseUrl); } HRESULT CProcessChannel::CheckPreCache() { BOOL fPreCache; if (SUCCEEDED(GetXMLBoolAttribute(m_pElement, L"PreCache", &fPreCache))) { if (fPreCache) return S_OK; return S_FALSE; } return S_OK; } HRESULT CProcessChannel::Run() { // Process Channel attributes, then any sub elements if (0 == m_lIndex) { m_lIndex ++; BSTR bstrURL=NULL; LPWSTR pwszUrl=NULL; HRESULT hr = S_OK; ASSERT(!m_bstrBaseUrl); // Get base URL if specified GetXMLStringAttribute(m_pElement, L"BASE", &m_bstrBaseUrl); if (SUCCEEDED(GetXMLStringAttribute(m_pElement, L"HREF", &bstrURL)) && bstrURL) CombineWithBaseUrl(bstrURL, &pwszUrl); if (pwszUrl && (m_pRoot==m_pParent)) { // Use this as default "email url" WriteOLESTR(m_pRoot->m_pChannelAgent->GetStartItem(), c_szPropEmailURL, pwszUrl); } if (pwszUrl && m_pRoot->IsChannelFlagSet(CHANNEL_AGENT_PRECACHE_SOME) && (S_OK == CheckPreCache())) { if (E_PENDING == DoWebCrawl(m_pElement, pwszUrl)) { m_fDownloadedHREF = TRUE; hr = E_PENDING; } } // If no URL for this log, check if global log exists if (SUCCEEDED(m_pRoot->DoTrackingFromItem(m_pElement, pwszUrl, m_pParent->IsGlobalLog()))) { SetGlobalLogFlag(TRUE); } SAFELOCALFREE(pwszUrl); SAFEFREEBSTR(bstrURL); if (hr == E_PENDING) return hr; } // We've processed attributes. Run sub-elements. return CProcessElement::Run(); } HRESULT CProcessChannel::ProcessItemInEnum(LPCWSTR pwszTagName, IXMLElement *pItem) { HRESULT hr; BSTR bstrTemp; if (!StrCmpIW(pwszTagName, L"Logo")) { if (ShouldDownloadLogo(pItem)) return DoWebCrawl(pItem); else return S_OK; } else if (!StrCmpIW(pwszTagName, L"Item")) { return DoChild(new CProcessItem(this, m_pRoot, pItem)); } else if (!StrCmpIW(pwszTagName, L"Channel")) { return DoChild(new CProcessChannel(this, m_pRoot, pItem)); } /* else if (!StrCmpIW(pwszTagName, L"Login")) { // No sub-elements to process. Do it here. return m_pRoot->ProcessLogin(pItem); } */ else if (!StrCmpIW(pwszTagName, L"LOGTARGET")) { return m_pRoot->DoTrackingFromLog(pItem); } else if (!StrCmpIW(pwszTagName, L"Schedule")) { if (m_pRoot->IsChannelFlagSet(CHANNEL_AGENT_DYNAMIC_SCHEDULE)) return DoChild(new CProcessSchedule(this, m_pRoot, pItem)); else return S_OK; } else if (!StrCmpIW(pwszTagName, L"SoftPkg")) { return DoSoftDist(pItem); } else if (!StrCmpIW(pwszTagName, L"A")) { // Process Anchor tag if (!m_fDownloadedHREF && (m_pRoot->IsChannelFlagSet(CHANNEL_AGENT_PRECACHE_SOME) || (m_pRoot==m_pParent)) && SUCCEEDED(GetXMLStringAttribute(pItem, L"HREF", &bstrTemp)) && bstrTemp) { LPWSTR pwszUrl=NULL; hr = S_OK; CombineWithBaseUrl(bstrTemp, &pwszUrl); // not really necessary (a href) if (pwszUrl) { // Use this as default "email url" if (m_pRoot == m_pParent) WriteOLESTR(m_pRoot->m_pChannelAgent->GetStartItem(), c_szPropEmailURL, pwszUrl); if (m_pRoot->IsChannelFlagSet(CHANNEL_AGENT_PRECACHE_SOME) && (S_OK == CheckPreCache())) { hr = DoWebCrawl(m_pElement, pwszUrl); if (E_PENDING == hr) m_fDownloadedHREF = TRUE; // Process tracking for this item if (SUCCEEDED(m_pRoot->DoTrackingFromItem(m_pElement, pwszUrl, m_pParent->IsGlobalLog()))) SetGlobalLogFlag(TRUE); } } SAFELOCALFREE(pwszUrl); SysFreeString(bstrTemp); return hr; } return S_OK; } return S_OK; } CProcessItem::CProcessItem(CProcessElementSink *pParent, CProcessRoot *pRoot, IXMLElement *pItem) : CProcessElement(pParent, pRoot, pItem) { } CProcessItem::~CProcessItem() { SAFEFREEBSTR(m_bstrAnchorURL); } HRESULT CProcessItem::ProcessItemInEnum(LPCWSTR pwszTagName, IXMLElement *pItem) { if (!StrCmpIW(pwszTagName, L"Logo")) { if (ShouldDownloadLogo(pItem)) return DoWebCrawl(pItem); else return S_OK; } else if (!StrCmpIW(pwszTagName, L"Usage")) { // Usage tag found. BSTR bstrValue; if (SUCCEEDED(GetXMLStringAttribute(pItem, L"Value", &bstrValue))) { if (!m_fDesktop && !StrCmpIW(bstrValue, L"DesktopComponent")) { m_fDesktop = TRUE; } if (!m_fEmail && !StrCmpIW(bstrValue, L"Email")) { m_fEmail = TRUE; } SysFreeString(bstrValue); } } else if (!StrCmpIW(pwszTagName, L"A")) { // Anchor tag found; save URL if (!m_bstrAnchorURL) GetXMLStringAttribute(pItem, L"HREF", &m_bstrAnchorURL); } return S_OK; } HRESULT CProcessItem::EnumerationComplete() { BOOL fPreCache, fPreCacheValid=FALSE; BOOL fDoDownload=FALSE; BSTR bstrURL=NULL; HRESULT hr = S_OK; LPWSTR pwszUrl=NULL; // End PCN compat if (SUCCEEDED(GetXMLBoolAttribute(m_pElement, L"PreCache", &fPreCache))) { fPreCacheValid = TRUE; } // Get the URL from our attribute, or from Anchor tag if not available if (FAILED(GetXMLStringAttribute(m_pElement, L"HREF", &bstrURL)) || !bstrURL) { bstrURL = m_bstrAnchorURL; m_bstrAnchorURL = NULL; } // Get the combined URL if (bstrURL) CombineWithBaseUrl(bstrURL, &pwszUrl); if (pwszUrl) { // Process tracking for this item m_pRoot->DoTrackingFromItem(m_pElement, pwszUrl, IsGlobalLog()); // Find if we should use this url for the Email agent if (m_fEmail) { // Yes, put this url in the end report DBG("Using custom email url"); WriteOLESTR(m_pRoot->m_pChannelAgent->GetStartItem(), c_szPropEmailURL, pwszUrl); } // Figure out if we should download our "href" based on Usage and Precache tag if (fPreCacheValid) { if (fPreCache) { if (m_pRoot->IsChannelFlagSet(CHANNEL_AGENT_PRECACHE_SOME)) fDoDownload = TRUE; } } else { if (m_pRoot->IsChannelFlagSet(CHANNEL_AGENT_PRECACHE_ALL)) fDoDownload = TRUE; } // if (m_fDesktop) // Do something for desktop components if (fDoDownload && pwszUrl) hr = DoWebCrawl(m_pElement, pwszUrl); } // pwszUrl SAFEFREEBSTR(bstrURL); SAFELOCALFREE(pwszUrl); return hr; } CProcessSchedule::CProcessSchedule(CProcessElementSink *pParent, CProcessRoot *pRoot, IXMLElement *pItem) : CProcessElement(pParent, pRoot, pItem) { } HRESULT CProcessSchedule::Run() { // Get attributes (Start and End date) first BSTR bstr=NULL; long lTimeZone; if (FAILED(GetXMLStringAttribute(m_pElement, L"StartDate", &bstr)) || !ISODateToSystemTime(bstr, &m_stStartDate, &lTimeZone)) { GetLocalTime(&m_stStartDate); } SAFEFREEBSTR(bstr); if (FAILED(GetXMLStringAttribute(m_pElement, L"StopDate", &bstr)) || !ISODateToSystemTime(bstr, &m_stEndDate, &lTimeZone)) { ZeroMemory(&m_stEndDate, sizeof(m_stEndDate)); } SAFEFREEBSTR(bstr); return CProcessElement::Run(); } HRESULT CProcessSchedule::ProcessItemInEnum(LPCWSTR pwszTagName, IXMLElement *pItem) { if (!StrCmpIW(pwszTagName, L"IntervalTime")) { GetXMLTimeAttributes(pItem, &m_timeInterval); } else if (!StrCmpIW(pwszTagName, L"EarliestTime")) { GetXMLTimeAttributes(pItem, &m_timeEarliest); } else if (!StrCmpIW(pwszTagName, L"LatestTime")) { GetXMLTimeAttributes(pItem, &m_timeLatest); } return S_OK; } HRESULT CProcessSchedule::EnumerationComplete() { DBG("CProcessSchedule::EnumerationComplete"); int iZone; if (FAILED(GetXMLTimeZoneAttribute(m_pElement, L"TimeZone", &iZone))) iZone = 9999; m_tt.cbTriggerSize = sizeof(m_tt); // m_pRoot is null for XMLElementToTaskTrigger call // Always run ScheduleToTaskTrigger if (SUCCEEDED(ScheduleToTaskTrigger(&m_tt, &m_stStartDate, &m_stEndDate, (long) m_timeInterval.dwConvertedMinutes, (long) m_timeEarliest.dwConvertedMinutes, (long) m_timeLatest.dwConvertedMinutes, iZone)) && m_pRoot) { SUBSCRIPTIONITEMINFO sii = { sizeof(SUBSCRIPTIONITEMINFO) }; if (SUCCEEDED(m_pRoot->m_pChannelAgent->GetStartItem()->GetSubscriptionItemInfo(&sii))) { if (sii.ScheduleGroup != GUID_NULL) { if (FAILED(UpdateScheduleTrigger(&sii.ScheduleGroup, &m_tt))) { DBG_WARN("Failed to update trigger in publisher's recommended schedule."); } } else DBG_WARN("No publisher's recommended schedule in sii"); } } return S_OK; } HRESULT ScheduleToTaskTrigger(TASK_TRIGGER *ptt, SYSTEMTIME *pstStartDate, SYSTEMTIME *pstEndDate, long lInterval, long lEarliest, long lLatest, int iZone/*=9999*/) { // Convert our schedule info to a TASK_TRIGGER struct ASSERT(pstStartDate); int iZoneCorrectionMinutes=0; TIME_ZONE_INFORMATION tzi; long lRandom; if ((lInterval == 0) || (lInterval > 366 * MINUTES_PER_DAY)) { DBG_WARN("ScheduleToTaskTrigger: Invalid IntervalTime - failing"); return E_INVALIDARG; } if (ptt->cbTriggerSize < sizeof(TASK_TRIGGER)) { DBG_WARN("ScheduleToTaskTrigger: ptt->cbTriggerSize not initialized"); ASSERT(!"ScheduleToTaskTrigger"); return E_INVALIDARG; } // Fix any invalid stuff if (lInterval < MINUTES_PER_DAY) { // ROUND so that dwIntervalMinutes is an even divisor of one day lInterval = MINUTES_PER_DAY / (MINUTES_PER_DAY / lInterval); } else { // ROUND to nearest day lInterval = MINUTES_PER_DAY * ((lInterval + 12*60)/MINUTES_PER_DAY); } if (lEarliest >= lInterval) { DBG("Invalid EarliestTime specified. Fixing."); // Earliest >= Interval! lEarliest = lInterval-1; } if (lLatest < lEarliest) { DBG("Invalid LatestTime specified. Fixing."); // Latest < Earliest! lLatest = lEarliest; } if (lLatest-lEarliest > lInterval) { DBG("Invalid LatestTime specified. Fixing."); // Latest > Interval! lLatest = lEarliest+lInterval; } lRandom = lLatest - lEarliest; ASSERT(lRandom>=0 && lRandom<=lInterval); if (iZone != 9999) { int iCorrection; iCorrection = (60 * (iZone/100)) + (iZone % 100); if (iCorrection < -12*60 || iCorrection > 12*60) { DBG("ScheduleElementToTaskTrigger: Invalid timezone; ignoring"); } else { if (TIME_ZONE_ID_INVALID != GetTimeZoneInformation(&tzi)) { // tzi.bias has correction from client timezone to UTC (+8 for US west coast) // iCorrection has correction from UTC to server time zone (-5 for US east coast) // result is correction from server to client time zone (-3 for east to west coast) iZoneCorrectionMinutes = - (iCorrection + tzi.Bias + tzi.StandardBias); TraceMsg(TF_THISMODULE, "ServerTimeZone = %d, LocalBias = %d min, RelativeCorrection = %d min", iZone, tzi.Bias+tzi.StandardBias, iZoneCorrectionMinutes); } else { DBG_WARN("Unable to get local time zone. Not correcting for time zone."); } } } TraceMsg(TF_THISMODULE, "StartDate = %d/%d/%d StopDate = %d/%d/%d", (int)(pstStartDate->wMonth),(int)(pstStartDate->wDay),(int)(pstStartDate->wYear),(int)(pstEndDate->wMonth),(int)(pstEndDate->wDay),(int)(pstEndDate->wYear)); TraceMsg(TF_THISMODULE, "IntervalTime = %6d minutes", (int)lInterval); TraceMsg(TF_THISMODULE, "EarliestTime = %6d minutes", (int)lEarliest); TraceMsg(TF_THISMODULE, "LatestTime = %6d minutes", (int)lLatest); TraceMsg(TF_THISMODULE, "RandomTime = %6d minutes", (int)lRandom); if (iZoneCorrectionMinutes != 0) { if (lInterval % 60) { DBG("Not correcting for time zone ; interval not multiple of 1 hour"); } else { // Correct Earliest time for time zone lEarliest += (iZoneCorrectionMinutes % lInterval); if (lEarliest < 0) lEarliest += lInterval; TraceMsg(TF_THISMODULE, "EarliestTime = %6d minutes (after timezone)", (int)lEarliest); } } ZeroMemory(ptt, sizeof(*ptt)); ptt->cbTriggerSize = sizeof(*ptt); ptt->wBeginYear = pstStartDate->wYear; ptt->wBeginMonth = pstStartDate->wMonth; ptt->wBeginDay = pstStartDate->wDay; if (pstEndDate && pstEndDate->wYear) { ptt->rgFlags |= TASK_TRIGGER_FLAG_HAS_END_DATE; ptt->wEndYear = pstEndDate->wYear; ptt->wEndMonth = pstEndDate->wMonth; ptt->wEndDay = pstEndDate->wDay; } // Set up Random period ; difference between Latesttime and Earliesttime ptt->wRandomMinutesInterval = (WORD) lRandom; ptt->wStartHour = (WORD) (lEarliest / 60); ptt->wStartMinute = (WORD) (lEarliest % 60); // Set up according to IntervalTime if (lInterval < MINUTES_PER_DAY) { // Less than one day (1/2 day, 1/3 day, 1/4 day, etc) ptt->MinutesDuration = MINUTES_PER_DAY - lEarliest; ptt->MinutesInterval = lInterval; ptt->TriggerType = TASK_TIME_TRIGGER_DAILY; ptt->Type.Daily.DaysInterval = 1; } else { // Greater than or equal to one day. DWORD dwIntervalDays = lInterval / MINUTES_PER_DAY; TraceMsg(TF_THISMODULE, "Using %d day interval", dwIntervalDays); ptt->TriggerType = TASK_TIME_TRIGGER_DAILY; ptt->Type.Daily.DaysInterval = (WORD) dwIntervalDays; } return S_OK; } //============================================================================== // CRunDeliveryAgent provides generic support for synchronous operation of a // delivery agent // It is aggregatable so that you can add more interfaces to the callback //============================================================================== CRunDeliveryAgent::CRunDeliveryAgent() { m_cRef = 1; } HRESULT CRunDeliveryAgent::Init(CRunDeliveryAgentSink *pParent, ISubscriptionItem *pItem, REFCLSID rclsidDest) { ASSERT(pParent && pItem); if (m_pParent || m_pItem) return E_FAIL; // already initialized. can't reuse an instance. if (!pParent || !pItem) return E_FAIL; m_pParent = pParent; m_clsidDest = rclsidDest; m_pItem = pItem; pItem->AddRef(); return S_OK; } CRunDeliveryAgent::~CRunDeliveryAgent() { CleanUp(); } // // IUnknown members // STDMETHODIMP_(ULONG) CRunDeliveryAgent::AddRef(void) { return ++m_cRef; } STDMETHODIMP_(ULONG) CRunDeliveryAgent::Release(void) { if( 0L != --m_cRef ) return m_cRef; delete this; return 0L; } STDMETHODIMP CRunDeliveryAgent::QueryInterface(REFIID riid, void ** ppv) { *ppv=NULL; // Validate requested interface if ((IID_IUnknown == riid) || (IID_ISubscriptionAgentEvents == riid)) { *ppv=(ISubscriptionAgentEvents *)this; } else return E_NOINTERFACE; // Addref through the interface ((LPUNKNOWN)*ppv)->AddRef(); return S_OK; } // // ISubscriptionAgentEvents members // STDMETHODIMP CRunDeliveryAgent::UpdateBegin(const SUBSCRIPTIONCOOKIE *) { return S_OK; } STDMETHODIMP CRunDeliveryAgent::UpdateProgress( const SUBSCRIPTIONCOOKIE *, long lSizeDownloaded, long lProgressCurrent, long lProgressMax, HRESULT hrStatus, LPCWSTR wszStatus) { if (m_pParent) m_pParent->OnAgentProgress(); return S_OK; } STDMETHODIMP CRunDeliveryAgent::UpdateEnd(const SUBSCRIPTIONCOOKIE *pCookie, long lSizeDownloaded, HRESULT hrResult, LPCWSTR wszResult) { ASSERT((hrResult != INET_S_AGENT_BASIC_SUCCESS) && (hrResult != E_PENDING)); m_hrResult = hrResult; if (hrResult == INET_S_AGENT_BASIC_SUCCESS || hrResult == E_PENDING) { // Shouldn't happen; let's be robust anyway. m_hrResult = S_OK; } if (m_pParent) { m_pParent->OnAgentEnd(pCookie, lSizeDownloaded, hrResult, wszResult, m_fInStartAgent); } CleanUp(); return S_OK; } STDMETHODIMP CRunDeliveryAgent::ReportError( const SUBSCRIPTIONCOOKIE *pSubscriptionCookie, HRESULT hrError, LPCWSTR wszError) { return S_FALSE; } HRESULT CRunDeliveryAgent::StartAgent() { HRESULT hr; if (!m_pParent || !m_pItem || m_pAgent) return E_FAIL; AddRef(); // Release before we return from this function m_fInStartAgent = TRUE; m_hrResult = INET_S_AGENT_BASIC_SUCCESS; DBG("Using new interfaces to host agent"); ASSERT(!m_pAgent); hr = CoCreateInstance(m_clsidDest, NULL, CLSCTX_INPROC_SERVER, IID_ISubscriptionAgentControl, (void **)&m_pAgent); if (m_pAgent) { hr = m_pAgent->StartUpdate(m_pItem, (ISubscriptionAgentEvents *)this); } hr = m_hrResult; m_fInStartAgent = FALSE; Release(); if (hr != INET_S_AGENT_BASIC_SUCCESS) { return hr; } return E_PENDING; }; HRESULT CRunDeliveryAgent::AgentPause(DWORD dwFlags) { if (m_pAgent) return m_pAgent->PauseUpdate(0); DBG_WARN("CRunDeliveryAgent::AgentPause with no running agent!!"); return S_FALSE; } HRESULT CRunDeliveryAgent::AgentResume(DWORD dwFlags) { if (m_pAgent) return m_pAgent->ResumeUpdate(0); DBG_WARN("CRunDeliveryAgent::AgentResume with no running agent!!"); return E_FAIL; } HRESULT CRunDeliveryAgent::AgentAbort(DWORD dwFlags) { if (m_pAgent) return m_pAgent->AbortUpdate(0); DBG_WARN("CRunDeliveryAgent::AgentAbort with no running agent!!"); return S_FALSE; } void CRunDeliveryAgent::CleanUp() { SAFERELEASE(m_pItem); SAFERELEASE(m_pAgent); m_pParent = NULL; } ////////////////////////////////////////////////////////////////////////// // // CChannelAgentHolder, derives from CRunDeliveryAgent // ////////////////////////////////////////////////////////////////////////// CChannelAgentHolder::CChannelAgentHolder(CChannelAgent *pChannelAgent, CProcessElement *pProcess) { m_pChannelAgent = pChannelAgent; m_pProcess = pProcess; } CChannelAgentHolder::~CChannelAgentHolder() { } // Won't compile unless we have addref & release here. STDMETHODIMP_(ULONG) CChannelAgentHolder::AddRef(void) { return CRunDeliveryAgent::AddRef(); } STDMETHODIMP_(ULONG) CChannelAgentHolder::Release(void) { return CRunDeliveryAgent::Release(); } STDMETHODIMP CChannelAgentHolder::QueryInterface(REFIID riid, void ** ppv) { *ppv=NULL; if (IID_IServiceProvider == riid) { *ppv = (IServiceProvider *)this; } else return CRunDeliveryAgent::QueryInterface(riid, ppv); // Addref through the interface ((LPUNKNOWN)*ppv)->AddRef(); return S_OK; } // IQueryService // CLSID_ChannelAgent IID_ISubscriptionItem channel agent start item // CLSID_XMLDocument IID_IXMLElement current element STDMETHODIMP CChannelAgentHolder::QueryService(REFGUID guidService, REFIID riid, void **ppvObject) { ASSERT(ppvObject); if (!ppvObject) return E_INVALIDARG; if (!m_pChannelAgent || !m_pProcess || !m_pParent) return E_FAIL; *ppvObject = NULL; if (guidService == CLSID_ChannelAgent) { if (riid == IID_ISubscriptionItem) { *ppvObject = m_pChannelAgent->GetStartItem(); } // if (riid == IID_IXMLElement) Root XML document? } else if (guidService == CLSID_XMLDocument) { if (riid == IID_IXMLElement) { *ppvObject = m_pProcess->GetCurrentElement(); } } if (*ppvObject) { ((IUnknown *)*ppvObject)->AddRef(); return S_OK; } return E_FAIL; } ////////////////////////////////////////////////////////////////////////// // // CChannelAgent implementation // ////////////////////////////////////////////////////////////////////////// CChannelAgent::CChannelAgent() { DBG("Creating CChannelAgent object"); // Initialize object // Many vars are initialized in StartOperation m_pwszURL = NULL; m_pCurDownload = NULL; m_pProcess = NULL; m_fHasInitCookie = FALSE; m_pChannelIconHelper = NULL; } CChannelAgent::~CChannelAgent() { // DBG("Destroying CChannelAgent object"); if (m_pwszURL) CoTaskMemFree(m_pwszURL); SAFELOCALFREE (m_pBuf); ASSERT(!m_pProcess); SAFERELEASE(m_pChannelIconHelper); DBG("Destroyed CChannelAgent object"); } void CChannelAgent::CleanUp() { if (m_pCurDownload) { m_pCurDownload->LeaveMeAlone(); // no more calls from them m_pCurDownload->DoneDownloading(); m_pCurDownload->Release(); m_pCurDownload = NULL; } SAFEFREEOLESTR(m_pwszURL); SAFEDELETE(m_pProcess); SAFELOCALFREE(m_pBuf); CDeliveryAgent::CleanUp(); } HRESULT CChannelAgent::StartOperation() { DBG("Channel Agent in StartOperation"); DWORD dwTemp; SAFEFREEOLESTR(m_pwszURL); if (FAILED(ReadOLESTR(m_pSubscriptionItem, c_szPropURL, &m_pwszURL)) || !CUrlDownload::IsValidURL(m_pwszURL)) { DBG_WARN("Couldn't get valid URL, aborting"); SetEndStatus(E_INVALIDARG); SendUpdateNone(); return E_INVALIDARG; } if (FAILED(ReadDWORD(m_pSubscriptionItem, c_szPropChannelFlags, &m_dwChannelFlags))) m_dwChannelFlags = 0; // If we download all, we also download some. Makes assumptions easier. if (m_dwChannelFlags & CHANNEL_AGENT_PRECACHE_ALL) m_dwChannelFlags |= CHANNEL_AGENT_PRECACHE_SOME; // NOTE: We may want REST_NoChannelContent to be similar to the webcrawl version. // Probably not though because the headlines are useful in the UI. if (SHRestricted2W(REST_NoChannelContent, NULL, 0)) ClearFlag(m_dwChannelFlags, CHANNEL_AGENT_PRECACHE_ALL | CHANNEL_AGENT_PRECACHE_SOME); m_dwMaxSizeKB = SHRestricted2W(REST_MaxChannelSize, NULL, 0); if (SUCCEEDED(ReadDWORD(m_pSubscriptionItem, c_szPropCrawlMaxSize, &dwTemp)) && dwTemp && (0 == m_dwMaxSizeKB || dwTemp < m_dwMaxSizeKB)) { m_dwMaxSizeKB = dwTemp; } if (IsAgentFlagSet(FLAG_CHANGESONLY)) { ClearFlag(m_dwChannelFlags, CHANNEL_AGENT_PRECACHE_ALL| CHANNEL_AGENT_PRECACHE_SOME|CHANNEL_AGENT_PRECACHE_SCRNSAVER); DBG("Channel agent is in 'changes only' mode."); } else { // Read old group ID ReadLONGLONG(m_pSubscriptionItem, c_szPropCrawlGroupID, &m_llOldCacheGroupID); // Read new ID if present m_llCacheGroupID = 0; ReadLONGLONG(m_pSubscriptionItem, c_szPropCrawlNewGroupID, &m_llCacheGroupID); } return CDeliveryAgent::StartOperation(); } HRESULT CChannelAgent::StartDownload() { ASSERT(!m_pCurDownload); TraceMsg(TF_THISMODULE, "Channel agent starting download of CDF: URL=%ws", m_pwszURL); m_pCurDownload = new CUrlDownload(this, 0); if (!m_pCurDownload) return E_OUTOFMEMORY; // Change detection m_varChange.vt = VT_EMPTY; if (IsAgentFlagSet(FLAG_CHANGESONLY)) { // "Changes Only" mode, we have persisted a change detection code ReadVariant(m_pSubscriptionItem, c_szPropChangeCode, &m_varChange); m_llCacheGroupID = 0; } else { // Create new cache group if (!m_llCacheGroupID) { m_llCacheGroupID = CreateUrlCacheGroup(CACHEGROUP_FLAG_NONPURGEABLE, 0); ASSERT_MSG(m_llCacheGroupID != 0, "Create cache group failed"); } } TCHAR szUrl[INTERNET_MAX_URL_LENGTH]; MyOleStrToStrN(szUrl, INTERNET_MAX_URL_LENGTH, m_pwszURL); PreCheckUrlForChange(szUrl, &m_varChange, NULL); SendUpdateProgress(m_pwszURL, 0, -1, 0); // Start download return m_pCurDownload->BeginDownloadURL2( m_pwszURL, BDU2_URLMON, BDU2_NEEDSTREAM, NULL, m_dwMaxSizeKB<<10); } HRESULT CChannelAgent::OnAuthenticate(HWND *phwnd, LPWSTR *ppszUsername, LPWSTR *ppszPassword) { HRESULT hr; ASSERT(phwnd && ppszUsername && ppszPassword); ASSERT((HWND)-1 == *phwnd && NULL == *ppszUsername && NULL == *ppszPassword); hr = ReadOLESTR(m_pSubscriptionItem, c_szPropCrawlUsername, ppszUsername); if (SUCCEEDED(hr)) { BSTR bstrPassword = NULL; hr = ReadPassword(m_pSubscriptionItem, &bstrPassword); if (SUCCEEDED(hr)) { int len = (lstrlenW(bstrPassword) + 1) * sizeof(WCHAR); *ppszPassword = (LPWSTR) CoTaskMemAlloc(len); if (*ppszPassword) { CopyMemory(*ppszPassword, bstrPassword, len); } SAFEFREEBSTR(bstrPassword); if (*ppszPassword) { return S_OK; } } } SAFEFREEOLESTR(*ppszUsername); SAFEFREEOLESTR(*ppszPassword); return E_FAIL; } HRESULT CChannelAgent::OnDownloadComplete(UINT iID, int iError) { TraceMsg(TF_THISMODULE, "Channel Agent: OnDownloadComplete(%d)", iError); IStream *pStm = NULL; HRESULT hr; BOOL fProcessed=FALSE; DWORD dwCDFSizeKB=0, dwResponseCode; BSTR bstrTmp; char chBuf[MY_MAX_CACHE_ENTRY_INFO]; DWORD dwBufSize = sizeof(chBuf); LPINTERNET_CACHE_ENTRY_INFO lpInfo = (LPINTERNET_CACHE_ENTRY_INFO) chBuf; if (iError) hr = E_FAIL; else { hr = m_pCurDownload->GetResponseCode(&dwResponseCode); if (SUCCEEDED(hr)) { hr = CheckResponseCode(dwResponseCode); } else DBG_WARN("CChannelAgent failed to GetResponseCode"); } if (SUCCEEDED(hr)) { hr = m_pCurDownload->GetStream(&pStm); m_pCurDownload->ReleaseStream(); } if (SUCCEEDED(hr)) { TCHAR szThisUrl[INTERNET_MAX_URL_LENGTH]; LPWSTR pwszThisUrl; m_pCurDownload->GetRealURL(&pwszThisUrl); if (pwszThisUrl) { MyOleStrToStrN(szThisUrl, INTERNET_MAX_URL_LENGTH, pwszThisUrl); LocalFree(pwszThisUrl); if (SUCCEEDED(GetUrlInfoAndMakeSticky( NULL, szThisUrl, lpInfo, dwBufSize, m_llCacheGroupID))) { dwCDFSizeKB = (((LPINTERNET_CACHE_ENTRY_INFO)chBuf)->dwSizeLow+512) >> 10; TraceMsg(TF_THISMODULE, "CDF size %d kb", dwCDFSizeKB); hr = PostCheckUrlForChange(&m_varChange, lpInfo, lpInfo->LastModifiedTime); // If we FAILED, we mark it as changed. if (hr == S_OK || FAILED(hr)) { SetAgentFlag(FLAG_CDFCHANGED); DBG("CDF has changed; will flag channel as changed"); } // "Changes Only" mode, persist change detection code if (IsAgentFlagSet(FLAG_CHANGESONLY)) { WriteVariant(m_pSubscriptionItem, c_szPropChangeCode, &m_varChange); } hr = S_OK; } } } else { SetEndStatus(E_INVALIDARG); } // Get an object model on our Channel Description File if (SUCCEEDED(hr) && pStm) { IPersistStreamInit *pPersistStm=NULL; CoCreateInstance(CLSID_XMLDocument, NULL, CLSCTX_INPROC, IID_IPersistStreamInit, (void **)&pPersistStm); if (pPersistStm) { pPersistStm->InitNew(); hr = pPersistStm->Load(pStm); if (SUCCEEDED(hr)) { IXMLDocument *pDoc; hr = pPersistStm->QueryInterface(IID_IXMLDocument, (void **)&pDoc); if (SUCCEEDED(hr) && pDoc) { IXMLElement *pRoot; BSTR bstrCharSet=NULL; if (SUCCEEDED(pDoc->get_charset(&bstrCharSet)) && bstrCharSet) { WriteOLESTR(m_pSubscriptionItem, c_szPropCharSet, bstrCharSet); TraceMsg(TF_THISMODULE, "Charset = \"%ws\"", bstrCharSet); SysFreeString(bstrCharSet); } else WriteEMPTY(m_pSubscriptionItem, c_szPropCharSet); hr = pDoc->get_root(&pRoot); if (SUCCEEDED(hr) && pRoot) { if (SUCCEEDED(pRoot->get_tagName(&bstrTmp)) && bstrTmp) { if (!StrCmpIW(bstrTmp, L"Channel")) { ASSERT(!m_pProcess); m_pProcess = new CProcessRoot(this, pRoot); if (m_pProcess) { if (IsAgentFlagSet(FLAG_CDFCHANGED)) SetEndStatus(S_OK); else SetEndStatus(S_FALSE); m_pProcess->m_dwCurSizeKB = dwCDFSizeKB; WriteEMPTY(m_pSubscriptionItem, c_szPropEmailURL); hr = m_pProcess->Run(); // This will get us cleaned up (now or later) fProcessed = TRUE; // So we shouldn't do it ourselves } } else DBG_WARN("Valid XML but invalid CDF"); SAFEFREEBSTR(bstrTmp); } pRoot->Release(); } pDoc->Release(); } } pPersistStm->Release(); } } if (!fProcessed || (FAILED(hr) && (hr != E_PENDING))) { if (INET_S_AGENT_BASIC_SUCCESS == GetEndStatus()) SetEndStatus(E_FAIL); DBG_WARN("Failed to process CDF ; XML load failed?"); CleanUp(); // CleanUp only if the process failed (otherwise OnChildDone does it) } #ifdef DEBUG if (hr == E_PENDING) DBG("CChannelAgent::OnDownloadComplete not cleaning up, webcrawl pending"); #endif return S_OK; } HRESULT CChannelAgent::OnChildDone(CProcessElement *pChild, HRESULT hr) { // Our CProcessRoot has reported that it's done. Clean up. DBG("CChannelAgent::OnChildDone cleaning up Channel delivery agent"); if (m_llOldCacheGroupID) { DBG("Nuking old cache group."); if (!DeleteUrlCacheGroup(m_llOldCacheGroupID, 0, 0)) { DBG_WARN("Failed to delete old cache group!"); } } WriteLONGLONG(m_pSubscriptionItem, c_szPropCrawlGroupID, m_llCacheGroupID); // Add "total size" property m_lSizeDownloadedKB = (long) (m_pProcess->m_dwCurSizeKB); WriteDWORD(m_pSubscriptionItem, c_szPropCrawlActualSize, m_lSizeDownloadedKB); WriteDWORD(m_pSubscriptionItem, c_szPropActualProgressMax, m_pProcess->m_iTotalStarted); CleanUp(); return S_OK; } HRESULT CChannelAgent::AgentPause(DWORD dwFlags) { DBG("CChannelAgent::AgentPause"); if (m_pProcess) m_pProcess->Pause(dwFlags); return CDeliveryAgent::AgentPause(dwFlags); } HRESULT CChannelAgent::AgentResume(DWORD dwFlags) { DBG("CChannelAgent::AgentResume"); if (m_pProcess) m_pProcess->Resume(dwFlags); return CDeliveryAgent::AgentResume(dwFlags); } // Forcibly abort current operation HRESULT CChannelAgent::AgentAbort(DWORD dwFlags) { DBG("CChannelAgent::AgentAbort"); if (m_pCurDownload) m_pCurDownload->DoneDownloading(); if (m_pProcess) m_pProcess->Abort(dwFlags); return CDeliveryAgent::AgentAbort(dwFlags); } HRESULT CChannelAgent::ModifyUpdateEnd(ISubscriptionItem *pEndItem, UINT *puiRes) { // Customize our end status string switch (GetEndStatus()) { case INET_E_AGENT_MAX_SIZE_EXCEEDED : *puiRes = IDS_AGNT_STATUS_SIZELIMIT; break; case INET_E_AGENT_CACHE_SIZE_EXCEEDED : *puiRes = IDS_AGNT_STATUS_CACHELIMIT; break; case E_FAIL : *puiRes = IDS_CRAWL_STATUS_NOT_OK; break; case S_OK : if (!IsAgentFlagSet(FLAG_CHANGESONLY)) *puiRes = IDS_CRAWL_STATUS_OK; else *puiRes = IDS_URL_STATUS_OK; break; case S_FALSE : if (!IsAgentFlagSet(FLAG_CHANGESONLY)) *puiRes = IDS_CRAWL_STATUS_UNCHANGED; else *puiRes = IDS_URL_STATUS_UNCHANGED; break; case INET_S_AGENT_PART_FAIL : *puiRes = IDS_CRAWL_STATUS_MOSTLYOK; break; } return CDeliveryAgent::ModifyUpdateEnd(pEndItem, puiRes); } const GUID CLSID_CDFICONHANDLER = {0xf3ba0dc0, 0x9cc8, 0x11d0, {0xa5, 0x99, 0x0, 0xc0, 0x4f, 0xd6, 0x44, 0x35}}; extern HRESULT LoadWithCookie(LPCTSTR, POOEBuf, DWORD *, SUBSCRIPTIONCOOKIE *); // IExtractIcon members STDMETHODIMP CChannelAgent::GetIconLocation(UINT uFlags, LPTSTR szIconFile, UINT cchMax, int * piIndex, UINT * pwFlags) { DWORD dwSize; IChannelMgrPriv* pIChannelMgrPriv = NULL; HRESULT hr = E_FAIL; TCHAR szPath[MAX_PATH]; if (!m_pBuf) { m_pBuf = (POOEBuf)MemAlloc(LPTR, sizeof(OOEBuf)); if (!m_pBuf) return E_OUTOFMEMORY; HRESULT hr = LoadWithCookie(NULL, m_pBuf, &dwSize, &m_SubscriptionCookie); RETURN_ON_FAILURE(hr); } hr = GetChannelPath(m_pBuf->m_URL, szPath, ARRAYSIZE(szPath), &pIChannelMgrPriv); if (SUCCEEDED(hr) && pIChannelMgrPriv) { IPersistFile* ppf = NULL; BOOL bCoinit = FALSE; HRESULT hr2 = E_FAIL; pIChannelMgrPriv->Release(); hr = CoCreateInstance(CLSID_CDFICONHANDLER, NULL, CLSCTX_INPROC_SERVER, IID_IPersistFile, (void**)&ppf); if ((hr == CO_E_NOTINITIALIZED || hr == REGDB_E_IIDNOTREG) && SUCCEEDED(CoInitialize(NULL))) { bCoinit = TRUE; hr = CoCreateInstance(CLSID_CDFICONHANDLER, NULL, CLSCTX_INPROC_SERVER, IID_IPersistFile, (void**)&ppf); } if (SUCCEEDED(hr)) { hr = ppf->QueryInterface(IID_IExtractIcon, (void**)&m_pChannelIconHelper); WCHAR wszPath[MAX_PATH]; MyStrToOleStrN(wszPath, ARRAYSIZE(wszPath), szPath); hr2 = ppf->Load(wszPath, 0); ppf->Release(); } if (SUCCEEDED(hr) && m_pChannelIconHelper) { hr = m_pChannelIconHelper->GetIconLocation(uFlags, szIconFile, cchMax, piIndex, pwFlags); } if (bCoinit) CoUninitialize(); } if (m_pChannelIconHelper == NULL) { WCHAR wszCookie[GUIDSTR_MAX]; ASSERT (piIndex && pwFlags && szIconFile); StringFromGUID2(m_SubscriptionCookie, wszCookie, ARRAYSIZE(wszCookie)); MyOleStrToStrN(szIconFile, cchMax, wszCookie); *piIndex = 0; *pwFlags |= GIL_NOTFILENAME | GIL_PERINSTANCE; hr = NOERROR; } return hr; } STDMETHODIMP CChannelAgent::Extract(LPCTSTR szIconFile, UINT nIconIndex, HICON * phiconLarge, HICON * phiconSmall, UINT nIconSize) { static HICON channelIcon = NULL; if (!phiconLarge || !phiconSmall) return E_INVALIDARG; * phiconLarge = * phiconSmall = NULL; if (m_pChannelIconHelper) { return m_pChannelIconHelper->Extract(szIconFile, nIconIndex, phiconLarge, phiconSmall, nIconSize); } else { DWORD dwSize; if (!m_pBuf) { m_pBuf = (POOEBuf)MemAlloc(LPTR, sizeof(OOEBuf)); if (!m_pBuf) return E_OUTOFMEMORY; HRESULT hr = LoadWithCookie(NULL, m_pBuf, &dwSize, &m_SubscriptionCookie); RETURN_ON_FAILURE(hr); } BYTE bBuf[MY_MAX_CACHE_ENTRY_INFO]; LPINTERNET_CACHE_ENTRY_INFO pEntry = (INTERNET_CACHE_ENTRY_INFO *)bBuf; dwSize = sizeof(bBuf); if (GetUrlCacheEntryInfo(m_pBuf->m_URL, pEntry, &dwSize)) { SHFILEINFO sfi; UINT cbFileInfo = sizeof(sfi), uFlags = SHGFI_ICON | SHGFI_LARGEICON; if (NULL != SHGetFileInfo(pEntry->lpszLocalFileName, 0, &sfi, cbFileInfo, uFlags)) { ASSERT(sfi.hIcon); *phiconLarge = *phiconSmall = sfi.hIcon; return NOERROR; } } if (channelIcon == NULL) { channelIcon = LoadIcon(g_hInst, MAKEINTRESOURCE(IDI_CHANNEL)); ASSERT(channelIcon); } * phiconLarge = * phiconSmall = channelIcon; return NOERROR; } }