#ifndef _TAPI_OBJECT_WITH_SITE_H_ #define _TAPI_OBJECT_WITH_SITE_H_ /*++ Copyright (c) 1999 Microsoft Corporation Module Name: ObjectWithSite.h Abstract: The implementation of IObjectWithSite interface that allows for per-page persistent data to be stored in registry or as a cookie. --*/ #include #include // // this url is used to construct the URL for cookies -- a security measure // so a script applet cannot drop a cookie with the same name and data // and fool us into thinking it is our cookie // static const TCHAR gszHardCodedURL[] = _T("http://www.microsoft.com/"); // // the expiration date is needed to make the cookie persistent // static const TCHAR gszCookieData[] = _T("6; expires = Sat, 12-Sep-2099 00:00:00 GMT"); // // dummy suffix to be appended to the url string // static const TCHAR gszURLSuffix[] = _T("/url"); class CObjectWithSite : public IObjectWithSite { public: // // current validation level. used to determine whether the page is safe, // unsafe, or whether information from the user is needed // enum EnValidation { VALIDATED_SAFE, VALIDATED_SAFE_PERMANENT, VALIDATED_UNSAFE, UNVALIDATED }; public: // // store type // enum EnMechanism { COOKIES, REGISTRY }; CObjectWithSite(TCHAR const *pszStorageName) :m_pszURL(NULL), m_dwSecurityZone(URLZONE_UNTRUSTED), m_pUnkSite(NULL), m_pszStorageName(NULL) { SetStorageName(pszStorageName); } ~CObjectWithSite() { if (m_pszURL) { delete m_pszURL; m_pszURL = NULL; } if (m_pUnkSite) { m_pUnkSite->Release(); m_pUnkSite = NULL; } if (m_pszStorageName) { delete m_pszStorageName; m_pszStorageName = NULL; } } //////////////////////////// // // IObjectWithSite methods STDMETHOD(SetSite)(IUnknown *pUnkSite) { if ((NULL != pUnkSite) && IsBadCodePtr((FARPROC)pUnkSite)) { return E_POINTER; } s_ObjectWithSiteCritSection.Lock(); // // we are moving away from a page. this is the new page, as far as // validation logic is concerned, so invalidate the current page // if (NULL == pUnkSite) { Validate(UNVALIDATED); } // // Get URL and zone information for this site // // // Note: we could delay this until we are actually asked for // zone or URL, but this should not be a performance bottlneck // in our case, so do this now to keep the code simple. StoreURLAndZone(pUnkSite); // // replace the current site pointer with the new one // if (m_pUnkSite) { m_pUnkSite->Release(); } m_pUnkSite = pUnkSite; if (m_pUnkSite) { m_pUnkSite->AddRef(); } s_ObjectWithSiteCritSection.Unlock(); return S_OK; } STDMETHOD(GetSite)(REFIID riid, void **ppSite) { HRESULT hr = E_POINTER; if (!IsBadWritePtr(ppSite, sizeof(void*))) { s_ObjectWithSiteCritSection.Lock(); *ppSite = NULL; if (m_pUnkSite) { hr = m_pUnkSite->QueryInterface(riid, ppSite); } else { hr = E_FAIL; } s_ObjectWithSiteCritSection.Unlock(); } return hr; } // // has this page been validated? // EnValidation GetValidation() { // // if the page has not been validated, see if it is marked as safe // s_ObjectWithSiteCritSection.Lock(); if (UNVALIDATED == s_enValidation) { if (IsPageSafe()) { s_enValidation = VALIDATED_SAFE; } } EnValidation enValidation = s_enValidation; s_ObjectWithSiteCritSection.Unlock(); return enValidation; } // // validate page as safe, unsafe, or reset validation // EnValidation Validate(EnValidation enNewValidation) { s_ObjectWithSiteCritSection.Lock(); // // keep the validation before the change // EnValidation enOldValidation = s_enValidation; // // safe permanent is a special case: // if (VALIDATED_SAFE_PERMANENT == enNewValidation) { // // set persistent safety flag and // validate page as safe // MarkPageAsSafe(); enNewValidation = VALIDATED_SAFE; } // // change our validation level for this page // s_enValidation = enNewValidation; s_ObjectWithSiteCritSection.Unlock(); return enOldValidation; } BOOL IsIntranet() { // // if anything other that intranet assume internet -- a less secure zone // s_ObjectWithSiteCritSection.Lock(); BOOL bIntranet = ( m_dwSecurityZone == URLZONE_INTRANET ); s_ObjectWithSiteCritSection.Unlock(); return bIntranet; } //////////////////// // // HaveSite() // // return true if we have a site pointer // BOOL HaveSite() { s_ObjectWithSiteCritSection.Lock(); BOOL bHaveSite = FALSE; if (NULL != m_pUnkSite) { bHaveSite = TRUE; } s_ObjectWithSiteCritSection.Unlock(); return bHaveSite; } private: //////////////////////////// // // store the current url in the "safe" list // // // not thread safe, called from inside a lock // HRESULT MarkPageAsSafe(EnMechanism enMechanism = COOKIES) { // // if storage is invalid, the object has not been properly initialized // if (IsBadStringPtr(m_pszStorageName, -1)) { return E_UNEXPECTED; } // // is we don't have the url, can't do what we are asked // if (NULL == m_pszURL) { return S_FALSE; } // // if url is garbage, we have a problem // if ( IsBadStringPtr(m_pszURL, -1) ) { return E_FAIL; } HRESULT hr = E_FAIL; switch (enMechanism) { case REGISTRY: hr = MarkPageSafeInRegistry(m_pszStorageName); break; case COOKIES: hr = MarkPageSafeCookie(m_pszStorageName); break; default: break; } return hr; } // // Returns TRUE if the current page is in the safe list // // // not thread safe, called from inside a lock // BOOL IsPageSafe( EnMechanism enMechanism = COOKIES ) { // // if we cannot get safety marking for whatever reason, // return false // _ASSERTE(NULL != m_pszStorageName); if ( IsBadStringPtr(m_pszURL, -1) || IsBadStringPtr(m_pszStorageName, -1)) { return FALSE; } BOOL bSafe = FALSE; switch (enMechanism) { case REGISTRY: bSafe = IsPageSafeRegistry(m_pszStorageName); break; case COOKIES: bSafe = IsPageSafeCookie(m_pszStorageName); break; default: break; } return bSafe; } private: // // this method is only called from the constructor. not thread safe. // HRESULT SetStorageName(TCHAR const *pszStorageName) { // // calling this method invalidates the old storage name // so deallocate it before doing anything else // if (NULL != m_pszStorageName) { delete m_pszStorageName; m_pszStorageName = NULL; } // // argument must be valid // if (IsBadStringPtr(pszStorageName, -1)) { return E_POINTER; } // // allocate buffer for the new storage name // size_t nSize = _tcsclen(pszStorageName) + 1; m_pszStorageName = new TCHAR[nSize]; if (NULL == m_pszStorageName) { return E_OUTOFMEMORY; } _tcscpy(m_pszStorageName, pszStorageName); return S_OK; } // // cache the url string and security zone id // not thread safe must be called from inside a lock // HRESULT StoreURLAndZone(IUnknown *pUnkSite) { // // reset zone and deallocate URL, if it exists // m_dwSecurityZone = URLZONE_UNTRUSTED; if (m_pszURL) { delete m_pszURL; m_pszURL = NULL; } if (pUnkSite == NULL) { return S_OK; } // // use pUnkSite to get to IHTMLDocument2, which will give us the URL // // // these interfaces need to be released on exit. // smart pointers will do exactly what we need // HRESULT hr = E_FAIL; CComPtr pSite; if (FAILED(hr = pUnkSite->QueryInterface(IID_IOleClientSite, (LPVOID *) &pSite))) { return hr; } CComPtr pOleCtr; if (FAILED(hr = pSite->GetContainer(&pOleCtr))) { return hr; } CComPtr pDoc; if (FAILED(hr = pOleCtr->QueryInterface(IID_IHTMLDocument2, (LPVOID *) &pDoc))) { return hr; } // // get and keep the url // BSTR bstrURL; if (FAILED(hr = pDoc->get_URL(&bstrURL))) { return hr; } UINT nURLLength = SysStringLen(bstrURL) + 1; _ASSERTE(NULL == m_pszURL); m_pszURL = new TCHAR[nURLLength]; if (NULL == m_pszURL) { SysFreeString(bstrURL); return E_OUTOFMEMORY; } #ifdef _UNICODE _tcscpy(m_pszURL, bstrURL); #else int r = WideCharToMultiByte( CP_ACP, 0, bstrURL, nURLLength, m_pszURL, nURLLength, NULL, NULL ); if (0 == r) { SysFreeString(bstrURL); delete m_pszURL; m_pszURL = NULL; return E_FAIL; } #endif // // whatever follows '#' and '?' is "extra info" and is not considered // to be a part of the actual URL by Internet(Set/Get)Coookie. Extra // Info has no value for us -- so throw it out // TCHAR *psDelimiter = _tcsstr(m_pszURL, _T("#")); if (NULL != psDelimiter) { *psDelimiter = _T('\0'); } psDelimiter = _tcsstr(m_pszURL, _T("?")); if (NULL != psDelimiter) { *psDelimiter = _T('\0'); } // // at this point we cached the URL // now attempt to get the security zone. if we fail getting zone // information still keep the url. // // // Get security zone // CComPtr pSecMgr; hr = CoCreateInstance(CLSID_InternetSecurityManager, NULL, CLSCTX_INPROC_SERVER, IID_IInternetSecurityManager, (LPVOID *) &pSecMgr); if (pSecMgr == NULL) { SysFreeString(bstrURL); return hr; } hr = pSecMgr->MapUrlToZone(bstrURL, &m_dwSecurityZone, 0); // // if failed, reset url to untrusted, just in case // if ( FAILED(hr) ) { m_dwSecurityZone = URLZONE_UNTRUSTED; } SysFreeString(bstrURL); // // we should have at least the URL at this point // return S_OK; } // // drop a cookie for this page as an indicator that this page is safe // HRESULT MarkPageSafeCookie(TCHAR const *pszCookieName) { TCHAR *pszURL = NULL; // // generate the url for the cookie // remember to delete the returned string // GenerateURLString(&pszURL); if (NULL == pszURL) return E_OUTOFMEMORY; BOOL bReturn = InternetSetCookie(pszURL, pszCookieName, gszCookieData); delete pszURL; return (bReturn)?S_OK:E_FAIL; } // // presence of a cookie for this page is an indicator that it's safe // returns TRUE if the cookie exists. FALSE otherwise // BOOL IsPageSafeCookie(TCHAR const *pszCookieName) { // // m_pszURL was checked by the calling function and the object // is protected. m_pszURL should never be null here. // _ASSERTE(m_pszURL); // // same goes for pszCookieName // _ASSERTE(pszCookieName); BOOL bReturn = FALSE; BOOL bFinalReturn = FALSE; TCHAR *pszURL = NULL; // remember to delete the returned string GenerateURLString(&pszURL); if (NULL == pszURL) { return FALSE; } // // see how much data the cookie contains // DWORD dwCookieDataSize = 0; // // assuming the return code is TRUE if the method succeeds in getting // get the buffer size. the current documentation is not 100% clear // bReturn = InternetGetCookie(pszURL, pszCookieName, NULL, &dwCookieDataSize); // // dwCookieDataSize has the length of cookie data // if ( bReturn && dwCookieDataSize ) { // // allocate the buffer for cookie data // TCHAR *pCookieDataBuffer = new TCHAR[dwCookieDataSize]; if (NULL != pCookieDataBuffer) { // // all cookies for this page are returned in cookie data, // the name argument is ignored // bReturn = InternetGetCookie(pszURL, pszCookieName, pCookieDataBuffer, &dwCookieDataSize); // // is succeeded, parse cookie data buffer to see if the // cookie we are looking for is there // if ( bReturn && ( NULL != _tcsstr(pCookieDataBuffer, pszCookieName) ) ) { bFinalReturn = TRUE; } delete pCookieDataBuffer; pCookieDataBuffer = NULL; } } delete pszURL; pszURL = NULL; return bFinalReturn; } // // add a registry entry for this page as an indicator that the page is safe // returns TRUE if the registry entry exists // HRESULT MarkPageSafeInRegistry(TCHAR const *szRegistryKeyName) { _ASSERTE(m_pszURL); // // open the registry key. create if not there // DWORD dwDisposition = 0; HKEY hKey = 0; LONG rc = RegCreateKeyEx(HKEY_CURRENT_USER, szRegistryKeyName, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &dwDisposition); if ( rc == ERROR_SUCCESS ) { DWORD dwData = 0; // // add the current URL to the registry // rc = RegSetValueEx(hKey, m_pszURL, 0, REG_DWORD, (BYTE*)&dwData, sizeof(DWORD)); } if (hKey) { RegCloseKey(hKey); } hKey = NULL; if (rc == ERROR_SUCCESS ) { return S_OK; } else { return E_FAIL; } } // // presence of a registry entry for this page indicates that the // page is safe // BOOL IsPageSafeRegistry(TCHAR const *szRegistryKeyName) { DWORD dwDisposition = 0; HKEY hKey = 0; // // the default is not safe // if (NULL == m_pszURL) { return FALSE; } // // open the registry key where the page information is kept. // create if not there // LONG rc = RegCreateKeyEx(HKEY_CURRENT_USER, szRegistryKeyName, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY | KEY_READ, NULL, &hKey, &dwDisposition); if ( rc == ERROR_SUCCESS ) { DWORD dwDataType = 0; DWORD dwDataSize = 0; // // read the setting for the current page. // Note: we don't need the actual data, we just // want to see if the value exists // rc = RegQueryValueEx(hKey, m_pszURL, 0, &dwDataType, NULL, &dwDataSize ); } if (hKey) { RegCloseKey(hKey); } hKey = NULL; return (rc == ERROR_SUCCESS); } // // build the URL string based on the hardcoded URL and // the actual URL for this page. // we are hoping that the striing will be unique (per page) and no // mischevious scripting app can drop a cookie corresponding to // this URL // // Note: if the implementation of of Internet(Set/Get)Cookie changes // to have stricter validation for the URL string, this technique will // not work // void GenerateURLString(TCHAR **ppszURL) { // // the precondition is that m_pszURL exists // _ASSERT(NULL != m_pszURL); *ppszURL = NULL; // // alias the char pointer pointer to by *pszURL. // so it is easier to refer to. // TCHAR* &pszURL = *ppszURL; // // allocate memory for concatenated string // pszURL = new TCHAR[_tcslen(gszHardCodedURL) + _tcslen(m_pszURL) + _tcslen(gszURLSuffix) + 1]; // concatenate if (pszURL) { *pszURL = _T('\0'); _tcscat(pszURL, gszHardCodedURL); _tcscat(pszURL, m_pszURL); _tcscat(pszURL, gszURLSuffix); } } private: // // cached URL string // TCHAR *m_pszURL; // // cached security zone // DWORD m_dwSecurityZone; // // site for IObjectWithSite // IUnknown *m_pUnkSite; // // thread safety // static CComAutoCriticalSection s_ObjectWithSiteCritSection; // // the status of the current page // static EnValidation s_enValidation; // // name of the persistent cookie or registry key // TCHAR *m_pszStorageName; }; #endif // _TAPI_OBJECT_WITH_SITE_H_