#include "priv.h" #ifdef FEATURE_PICS #include "asyncrat.h" #include #include "dochost.h" #include /* There is a PicsQuery structure in the following global array for each * outstanding query. It records the address of the PicsData structure in * the corresponding w3doc, the window handle corresponding to the Mwin, * and a serial number. This way, RatingObtainQueryCallback can tell if * the page the query corresponds to still exists, before posting a message; * and PicsDataMessageLoop can tell if the doc still exists when the message * finally gets delivered. * * The array is dynamically allocated and is protected by the main HTML * critical section. */ HDSA g_haQueries = NULL; DWORD g_dwPicsSerial = 1L; const UINT c_cQueryAllocSize = 8; /* should be plenty by default */ UINT g_crefQueries = 0; /* AddPicsQuery - add an outstanding PICS query to the list, given a window * handle to send a completion message to. Returns the serial number of the * query for later reference. */ DWORD _AddPicsQuery(HWND hwnd) { ENTERCRITICAL; DWORD dwRet = 0; if (g_haQueries == NULL) { g_haQueries = DSA_Create(sizeof(PicsQuery), c_cQueryAllocSize); } if (g_haQueries != NULL) { PicsQuery q; q.dwSerial = ::g_dwPicsSerial++; q.hwnd = hwnd; q.lpvRatingDetails = NULL; if (DSA_InsertItem(g_haQueries, DA_LAST, &q) >= 0) dwRet = q.dwSerial; } LEAVECRITICAL; return dwRet; } /* RemovePicsQuery - remove an outstanding query based on its serial number. */ void _RemovePicsQuery(DWORD dwSerial) { ENTERCRITICAL; if (g_haQueries != NULL) { UINT cQueries = DSA_GetItemCount(g_haQueries); PicsQuery *pq = NULL; for (UINT i=0; idwSerial == dwSerial) break; } if (pq != NULL) { if (pq->lpvRatingDetails != NULL) ::RatingFreeDetails(pq->lpvRatingDetails); DSA_DeleteItem(g_haQueries, i); } } LEAVECRITICAL; } /* GetPicsQuery - get a copy of an outstanding PICS query record, given its * serial number. Returns TRUE if found. */ BOOL _GetPicsQuery(DWORD dwSerial, PicsQuery *pOut) { ENTERCRITICAL; PicsQuery *pq = NULL; if (g_haQueries != NULL) { UINT cQueries = DSA_GetItemCount(g_haQueries); for (UINT i=0; idwSerial == dwSerial) break; } if (pq != NULL) { *pOut = *pq; pq->lpvRatingDetails = NULL; /* caller's copy owns this now */ } } LEAVECRITICAL; return pq != NULL; } /* _RefPicsQueries - add a reference to the async query array */ void _RefPicsQueries(void) { ENTERCRITICAL; ++g_crefQueries; LEAVECRITICAL; } /* _ReleasePicsQueries - cleanup all memory associated with outstanding queries */ void _ReleasePicsQueries(void) { ENTERCRITICAL; if (!--g_crefQueries) { if (g_haQueries != NULL) { UINT cQueries = DSA_GetItemCount(g_haQueries); for (UINT i=0; ilpvRatingDetails != NULL) { RatingFreeDetails(pq->lpvRatingDetails); } } DSA_Destroy(g_haQueries); g_haQueries = NULL; // leave g_dwPicsSerial as it is, just in case we start up again } } LEAVECRITICAL; } /* PostPicsMessage - formats up a custom window message to signal that a * query is complete. Format is WM_PICS_STATUS(hresult,dwSerial). Other * information (the rating details blob obtained from RatingCheckUserAccess) * is stored in the query record for safekeeping. * * Returns TRUE if a message was posted successfully to the right window. */ BOOL _PostPicsMessage(DWORD dwSerial, HRESULT hr, LPVOID lpvRatingDetails) { BOOL fRet = FALSE; ENTERCRITICAL; if (g_haQueries != NULL) { PicsQuery *pq = NULL; UINT cQueries = DSA_GetItemCount(g_haQueries); for (UINT i=0; idwSerial == dwSerial) break; } if (pq != NULL) { pq->lpvRatingDetails = lpvRatingDetails; fRet = PostMessage(pq->hwnd, WM_PICS_ASYNCCOMPLETE, (WPARAM)hr, (LPARAM)dwSerial); if (!fRet) { /* oops, couldn't post message, don't keep copy of details */ pq->lpvRatingDetails = NULL; } } } LEAVECRITICAL; return fRet; } /* Class CPicsRootDownload manages the download of the root document of a * site, to get ratings from it. */ CPicsRootDownload::CPicsRootDownload(IOleCommandTarget *pctParent, BOOL fFrameIsOffline, BOOL fFrameIsSilent) { m_cRef = 1; m_pctParent = pctParent; m_pctParent->AddRef(); m_pole = NULL; m_pctObject = NULL; m_pBinding = NULL; m_fFrameIsOffline = fFrameIsOffline ? TRUE : FALSE; m_fFrameIsSilent = fFrameIsSilent ? TRUE : FALSE; } CPicsRootDownload::~CPicsRootDownload() { ATOMICRELEASE(m_pctParent); CleanUp(); ATOMICRELEASE(m_pBinding); ATOMICRELEASE(m_pBindCtx); } HRESULT CPicsRootDownload::StartDownload(IMoniker *pmk) { IUnknown *punk = NULL; HRESULT hr; hr = CreateBindCtx(0, &m_pBindCtx); if (FAILED(hr)) goto LErrExit; /* hr = m_pBindCtx->RegisterObjectParam(BROWSER_OPTIONS_OBJECT_NAME, (IBrowseControl *)this); if (FAILED(hr)) goto LErrExit; */ // // Associate the client site as an object parameter to this // bind context so that Trident can pick it up while processing // IPersistMoniker::Load(). // m_pBindCtx->RegisterObjectParam(WSZGUID_OPID_DocObjClientSite, SAFECAST(this, IOleClientSite*)); hr = RegisterBindStatusCallback(m_pBindCtx, (IBindStatusCallback *)this, 0, 0L); if (FAILED(hr)) goto LErrExit; hr = pmk->BindToObject(m_pBindCtx, NULL, IID_IUnknown, (LPVOID*)&punk); if (SUCCEEDED(hr) || hr==E_PENDING) { hr = S_OK; // // If moniker happen to return the object synchronously, emulate // OnDataAvailable callback and OnStopBinding. // if (punk) { OnObjectAvailable(IID_IUnknown, punk); OnStopBinding(hr, NULL); punk->Release(); } } else { /* OnStopBinding can be called by URLMON within the BindToObject * call in some cases. So, don't call it ourselves if it's * already been called (we can tell by looking whether our * bind context still exists). */ if (m_pBindCtx != NULL) { OnStopBinding(hr, NULL); } } LErrExit: if (FAILED(hr) && (m_pBindCtx != NULL)) { m_pBindCtx->Release(); m_pBindCtx = NULL; } return hr; } /* _NotifyEndOfDocument is used in all the error cases to make sure the caller * gets a notification of some sort. The case where this function does not * send a notification is if we have a valid OLE object -- in that case, we're * assuming that we have it because we know it supports PICS, therefore we're * expecting it to send such a notification to the parent itself. */ void CPicsRootDownload::_NotifyEndOfDocument(void) { if (m_pole == NULL) { if (m_pctParent != NULL) { m_pctParent->Exec(&CGID_ShellDocView, SHDVID_NOMOREPICSLABELS, 0, NULL, NULL); } } } HRESULT CPicsRootDownload::_Abort() { if (m_pBinding) { return m_pBinding->Abort(); } return S_FALSE; } void CPicsRootDownload::CleanUp() { _Abort(); if (m_pctObject != NULL) { VARIANTARG v; v.vt = VT_UNKNOWN; v.punkVal = NULL; m_pctObject->Exec(&CGID_ShellDocView, SHDVID_CANSUPPORTPICS, 0, &v, NULL); m_pctObject->Exec(NULL, OLECMDID_STOP, NULL, NULL, NULL); ATOMICRELEASE(m_pctObject); } LPOLECLIENTSITE pcs; if (m_pole && SUCCEEDED(m_pole->GetClientSite(&pcs)) && pcs) { if (pcs == SAFECAST(this, LPOLECLIENTSITE)) { m_pole->SetClientSite(NULL); } pcs->Release(); } ATOMICRELEASE(m_pole); } // IUnknown members STDMETHODIMP CPicsRootDownload::QueryInterface(REFIID riid, void **punk) { *punk = NULL; if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IsPicsBrowser)) *punk = (IUnknown *)(IBindStatusCallback *)this; else if (IsEqualIID(riid, IID_IBindStatusCallback)) *punk = (IBindStatusCallback *)this; else if (IsEqualIID(riid, IID_IOleClientSite)) *punk = (IOleClientSite *)this; else if (IsEqualIID(riid, IID_IServiceProvider)) *punk = (IServiceProvider *)this; else if (IsEqualIID(riid, IID_IDispatch)) *punk = (IDispatch *)this; if (*punk != NULL) { ((IUnknown *)(*punk))->AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHODIMP_(ULONG) CPicsRootDownload::AddRef(void) { ++m_cRef; TraceMsg(TF_SHDREF, "CPicsRootDownload(%x)::AddRef called, new m_cRef=%d", this, m_cRef); return m_cRef; } STDMETHODIMP_(ULONG) CPicsRootDownload::Release(void) { UINT crefNew = --m_cRef; TraceMsg(TF_SHDREF, "CPicsRootDownload(%x)::Release called, new m_cRef=%d", this, m_cRef); if (!crefNew) delete this; return crefNew; } // IBindStatusCallback methods STDMETHODIMP CPicsRootDownload::OnStartBinding(DWORD dwReserved, IBinding* pbinding) { if (m_pBinding != NULL) m_pBinding->Release(); m_pBinding = pbinding; if (m_pBinding != NULL) m_pBinding->AddRef(); return S_OK; } STDMETHODIMP CPicsRootDownload::GetPriority(LONG* pnPriority) { return E_NOTIMPL; } STDMETHODIMP CPicsRootDownload::OnLowResource(DWORD dwReserved) { return E_NOTIMPL; } STDMETHODIMP CPicsRootDownload::OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR pwzStatusText) { /* If the root document's data type is not HTML, don't try to get any * ratings out of it, just abort. */ if (ulStatusCode == BINDSTATUS_CLASSIDAVAILABLE) { BOOL fContinueDownload = FALSE; CLSID clsid; // CLSIDFromString is prototyped wrong, non const first param HRESULT hresT = CLSIDFromString((WCHAR *)pwzStatusText, &clsid); if (SUCCEEDED(hresT)) { LPWSTR pwzProgID = NULL; hresT = ProgIDFromCLSID(clsid, &pwzProgID); if (SUCCEEDED(hresT)) { if (StrCmp(pwzProgID, L"htmlfile") == 0) { fContinueDownload = TRUE; } OleFree(pwzProgID); } } if (!fContinueDownload) { _Abort(); } } return S_OK; } STDMETHODIMP CPicsRootDownload::OnStopBinding(HRESULT hrResult, LPCWSTR szError) { /* Some of the cleanup we do in here (RevokeObjectParam is suspect?) could * remove our last reference, causing the Releases at the end to fault. * Guard against this with an AddRef/Release. Dochost does this too. * * WARNING - if URLMON is calling back through this object, shouldn't he * have a reference to us? If so, where is it? */ AddRef(); /* Notify the caller that we've got to the end of the document */ _NotifyEndOfDocument(); m_pBindCtx->RevokeObjectParam(WSZGUID_OPID_DocObjClientSite); ::RevokeBindStatusCallback(m_pBindCtx, (IBindStatusCallback *)this); ATOMICRELEASE(m_pBinding); ATOMICRELEASE(m_pBindCtx); /* Undo above AddRef(). */ Release(); return S_OK; } void SetBindfFlagsBasedOnAmbient(BOOL fAmbientOffline, DWORD *pgrfBindf); STDMETHODIMP CPicsRootDownload::GetBindInfo(DWORD* pgrfBINDF, BINDINFO* pbindInfo) { if ( !pgrfBINDF || !pbindInfo || !pbindInfo->cbSize ) return E_INVALIDARG; *pgrfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE; *pgrfBINDF |= BINDF_GETNEWESTVERSION; if(m_fFrameIsSilent) { *pgrfBINDF |= BINDF_NO_UI; } else { *pgrfBINDF &= ~BINDF_NO_UI; } SetBindfFlagsBasedOnAmbient(BOOLIFY(m_fFrameIsOffline), pgrfBINDF); // clear BINDINFO except cbSize DWORD cbSize = pbindInfo->cbSize; ZeroMemory( pbindInfo, cbSize ); pbindInfo->cbSize = cbSize; pbindInfo->dwBindVerb = BINDVERB_GET; return S_OK; } STDMETHODIMP CPicsRootDownload::OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pfmtetc, STGMEDIUM* pstgmed) { return E_NOTIMPL; } STDMETHODIMP CPicsRootDownload::OnObjectAvailable(REFIID riid, IUnknown* punk) { if (SUCCEEDED(punk->QueryInterface(IID_IOleCommandTarget, (LPVOID *)&m_pctObject))) { VARIANTARG v; v.vt = VT_UNKNOWN; v.punkVal = (IOleCommandTarget *)m_pctParent; HRESULT hresT = m_pctObject->Exec(&CGID_ShellDocView, SHDVID_CANSUPPORTPICS, 0, &v, NULL); if (hresT == S_OK) { hresT = punk->QueryInterface(IID_IOleObject, (LPVOID *)&m_pole); if (FAILED(hresT)) m_pole = NULL; } } if (m_pole == NULL) { ATOMICRELEASE(m_pctObject); _Abort(); } return S_OK; } // IOleClientSite STDMETHODIMP CPicsRootDownload::SaveObject(void) { return E_NOTIMPL; } STDMETHODIMP CPicsRootDownload::GetMoniker(DWORD, DWORD, IMoniker **) { return E_NOTIMPL; } STDMETHODIMP CPicsRootDownload::GetContainer(IOleContainer **) { return E_NOTIMPL; } STDMETHODIMP CPicsRootDownload::ShowObject(void) { return E_NOTIMPL; } STDMETHODIMP CPicsRootDownload::OnShowWindow(BOOL fShow) { return E_NOTIMPL; } STDMETHODIMP CPicsRootDownload::RequestNewObjectLayout(void) { return E_NOTIMPL; } // IServiceProvider (must be QI'able from IOleClientSite) STDMETHODIMP CPicsRootDownload::QueryService(REFGUID guidService, REFIID riid, void **ppvObj) { if (IsEqualGUID(guidService, SID_STopLevelBrowser)) { if (IsEqualIID(riid, IID_IsPicsBrowser)) return QueryInterface(riid, ppvObj); return E_NOINTERFACE; } return E_FAIL; } // IDispatch HRESULT CPicsRootDownload::Invoke(DISPID dispidMember, REFIID iid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pdispparams, VARIANT FAR* pVarResult,EXCEPINFO FAR* pexcepinfo,UINT FAR* puArgErr) { if (!pVarResult) return E_INVALIDARG; if (wFlags == DISPATCH_PROPERTYGET) { switch (dispidMember) { case DISPID_AMBIENT_DLCONTROL : // We support IDispatch so that Trident can ask us to control the // download. By specifying all the following flags, and by NOT // specifying DLCTL_DLIMAGES, DLCTL_VIDEOS, or DLCTL_BGSOUNDS, // we ensure we only download the HTML doc itself, and not a lot // of associated things that aren't going to help us find a META // tag. pVarResult->vt = VT_I4; pVarResult->lVal = DLCTL_SILENT | DLCTL_NO_SCRIPTS | DLCTL_NO_JAVA | DLCTL_NO_RUNACTIVEXCTLS | DLCTL_NO_DLACTIVEXCTLS | DLCTL_NO_FRAMEDOWNLOAD | DLCTL_NO_CLIENTPULL; break; default: return DISP_E_MEMBERNOTFOUND; } return S_OK; } return DISP_E_MEMBERNOTFOUND; } #endif /* FEATURE_PICS */