#include #include "cookieprompt.h" // checks a string to see if its a domain BOOL IsStringADomain( LPCSTR pszString) { int iLength = 0; bool fLastCharWasDot = false; while( pszString[iLength] != '\0') { if( fLastCharWasDot && pszString[iLength] == '.') return FALSE; fLastCharWasDot = pszString[iLength] == '.'; if( !(IsCharAlphaNumericA( pszString[iLength]) || pszString[iLength] == '.' || pszString[iLength] == '-')) { return FALSE; } iLength++; } return iLength > 0 ? TRUE : FALSE; } LPCSTR FindMinimizedCookieDomainInDomain( LPCSTR pszDomain) { LPCSTR pMinimizedDomain = pszDomain + strlen( pszDomain); do { pMinimizedDomain--; while( pszDomain < pMinimizedDomain && *(pMinimizedDomain-1) != L'.') { pMinimizedDomain--; } } while( !IsDomainLegalCookieDomainA( pMinimizedDomain, pszDomain) && pszDomain < pMinimizedDomain); return pMinimizedDomain; } CCookiePromptHistory::CCookiePromptHistory(const char *pchRegistryPath, bool fUseHKLM) { _fUseHKLM = fUseHKLM; lstrcpyn(_szRootKeyName, pchRegistryPath, sizeof(_szRootKeyName)/sizeof(_szRootKeyName[0])); _hkHistoryRoot = NULL; } CCookiePromptHistory::~CCookiePromptHistory() { if (_hkHistoryRoot) { RegCloseKey(_hkHistoryRoot); _hkHistoryRoot = NULL; } } HKEY CCookiePromptHistory::OpenRootKey() { HKEY hkey; if (_hkHistoryRoot == NULL) { if (RegCreateKeyEx(_fUseHKLM ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, _szRootKeyName, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS, NULL, &hkey, NULL) == ERROR_SUCCESS) { // if we aren't running in a service, we can cache the key if (!GlobalIsProcessNtService) { if (InterlockedCompareExchangePointer((void**)&_hkHistoryRoot, (void*)hkey, NULL)) { // someone beat us in the race to fill in _hkHistoryRoot, close ours since we // failed to set it into _hkHistoryRoot RegCloseKey(hkey); hkey = _hkHistoryRoot; } } } else { hkey = NULL; } } else { // use the cached value hkey = _hkHistoryRoot; } return hkey; } BOOL CCookiePromptHistory::CloseRootKey(HKEY hkeyRoot) { BOOL bClosedKey = FALSE; if (hkeyRoot) { if (GlobalIsProcessNtService) { // we never cache the key when runnint in a service! INET_ASSERT(_hkHistoryRoot == NULL); RegCloseKey(hkeyRoot); bClosedKey = TRUE; } else { INET_ASSERT(_hkHistoryRoot == hkeyRoot); } } return bClosedKey; } /* Lookup user-decision for given host+policy combination. If "pchPolicyID" is NULL the decision applies regardless of policy. */ BOOL CCookiePromptHistory::lookupDecision(const char *pchHostName, const char *pchPolicyID, unsigned long *pdwDecision) { BOOL fRet = FALSE; HKEY hSiteKey; CHAR szBuffer[ INTERNET_MAX_URL_LENGTH]; DWORD dwBufferSize = INTERNET_MAX_URL_LENGTH; if (SUCCEEDED( UrlUnescape( (LPSTR)pchHostName, szBuffer, &dwBufferSize, 0)) // forced LPSTR conv necessary because COULD be inplace unescape && IsStringADomain( szBuffer)) { HKEY hkHistoryRoot = OpenRootKey(); if (hSiteKey = lookupSiteKey(hkHistoryRoot, FindMinimizedCookieDomainInDomain(szBuffer))) { DWORD dwType, dwCookieState; DWORD dwSize = sizeof(dwCookieState); if (ERROR_SUCCESS == RegQueryValueEx(hSiteKey, pchPolicyID, 0, &dwType, (LPBYTE) &dwCookieState, &dwSize) && (dwType==REG_DWORD)) { *pdwDecision = dwCookieState; fRet = TRUE; } RegCloseKey(hSiteKey); } CloseRootKey(hkHistoryRoot); } //commented code - legacy design where we allowed rules on a non-minimized domain // while (pchHostName && !fRet) // { // if (hSiteKey=lookupSiteKey(pchHostName)) { // // DWORD dwType, dwCookieState; // DWORD dwSize = sizeof(dwCookieState); // // if (ERROR_SUCCESS == RegQueryValueEx(hSiteKey, pchPolicyID, 0, &dwType, (LPBYTE) &dwCookieState, &dwSize) // && (dwType==REG_DWORD)) { // // *pdwDecision = dwCookieState; // fRet = TRUE; // } // // RegCloseKey(hSiteKey); // } // // /* Find and skip over next dot if there is one */ // if (pchHostName = strchr(pchHostName, '.')) // pchHostName++; // } return fRet; } /* Save user-decision for given host+policy combination. If "pchPolicyID" is NULL the decision applies to all policies */ BOOL CCookiePromptHistory::saveDecision(const char *pchHostName, const char *pszPolicyID, unsigned long dwDecision) { BOOL fRet = FALSE; HKEY hSiteKey; CHAR szBuffer[ INTERNET_MAX_URL_LENGTH]; DWORD dwBufferSize = INTERNET_MAX_URL_LENGTH; if (SUCCEEDED( UrlUnescape( (LPSTR)pchHostName, szBuffer, &dwBufferSize, 0)) // forced LPSTR conv necessary because COULD be inplace unescape && IsStringADomain( szBuffer)) { HKEY hkHistoryRoot = OpenRootKey(); if (hSiteKey = lookupSiteKey(hkHistoryRoot, FindMinimizedCookieDomainInDomain( szBuffer), true)) { if (ERROR_SUCCESS == RegSetValueEx(hSiteKey, pszPolicyID, 0, REG_DWORD, (LPBYTE) &dwDecision, sizeof(dwDecision))) fRet = TRUE; RegCloseKey(hSiteKey); } CloseRootKey(hkHistoryRoot); } return fRet; } /* Clear previously saved decision for given hostname+policy combination. If the policy-ID is "*" all decisions about the site are cleared. */ BOOL CCookiePromptHistory::clearDecision(const char *pchHostName, const char *pchPolicyID) { BOOL fRet = FALSE; int error = ERROR_SUCCESS; HKEY hkHistoryRoot = OpenRootKey(); if ( pchPolicyID != NULL && !strcmp(pchPolicyID, "*")) { error = SHDeleteKey(hkHistoryRoot, pchHostName); } else if (HKEY hSiteKey = lookupSiteKey(hkHistoryRoot, pchHostName, false)) { error = RegDeleteValue(hSiteKey, pchPolicyID); RegCloseKey(hSiteKey); } CloseRootKey(hkHistoryRoot); /* If neither of the previous conditionals were TRUE, then there is no decision corresponding to that hostname */ return (error==ERROR_SUCCESS); } HKEY CCookiePromptHistory::lookupSiteKey(HKEY hkHistoryRoot, const char *pchHostName, bool fCreate) { HKEY hSiteKey = NULL; if (hkHistoryRoot) { LONG error; if (fCreate) { RegCreateKeyEx(hkHistoryRoot, pchHostName, 0, NULL, 0, KEY_QUERY_VALUE | KEY_SET_VALUE, NULL, &hSiteKey, NULL); } else { RegOpenKeyEx(hkHistoryRoot, pchHostName, 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &hSiteKey); } } return hSiteKey; } BOOL CCookiePromptHistory::clearAll() { DWORD dwIndex = 0; DWORD dwRet; HKEY hkHistoryRoot = OpenRootKey(); do { FILETIME ft; char achHostName[INTERNET_MAX_HOST_NAME_LENGTH]; DWORD dwNameLen = sizeof(achHostName); dwRet = RegEnumKeyEx(hkHistoryRoot, dwIndex, achHostName, & dwNameLen, NULL, NULL, NULL, &ft); if (dwRet == ERROR_SUCCESS) { if (SHDeleteKey(hkHistoryRoot, achHostName) != ERROR_SUCCESS) { dwIndex++; } } } while (dwRet == ERROR_SUCCESS); CloseRootKey(hkHistoryRoot); return TRUE; } unsigned long CCookiePromptHistory::enumerateDecisions(char *pchSiteName, unsigned long *pcbName, unsigned long *pdwDecision, unsigned long dwIndex) { FILETIME ft; HKEY hkHistoryRoot = OpenRootKey(); DWORD dwRet = RegEnumKeyEx(hkHistoryRoot, dwIndex, pchSiteName, pcbName, NULL, NULL, NULL, &ft); if (dwRet==ERROR_SUCCESS) { if (HKEY hSiteKey = lookupSiteKey(hkHistoryRoot, pchSiteName, false)) { DWORD dwType; DWORD dwSize = sizeof(DWORD); dwRet = RegQueryValueEx(hSiteKey, NULL, 0, &dwType, (LPBYTE) pdwDecision, &dwSize); RegCloseKey(hSiteKey); } else { dwRet = ERROR_NO_DATA; } } CloseRootKey(hkHistoryRoot); return dwRet; } /* Exported APIs for manipulating per-site cookie settings */ extern CCookiePromptHistory cookieUIhistory; BOOL DeletePersistentCookies(const char *pszDomainSuffix); INTERNETAPI_(BOOL) InternetSetPerSiteCookieDecisionA( IN LPCSTR pchHostName, DWORD dwDecision) { BOOL retVal = FALSE; if( !pchHostName || IsBadStringPtr(pchHostName, INTERNET_MAX_URL_LENGTH)) { SetLastError( ERROR_INVALID_PARAMETER); return FALSE; } if( dwDecision == COOKIE_STATE_UNKNOWN) { retVal = cookieUIhistory.clearDecision( pchHostName, "*"); } else if ( (dwDecision == COOKIE_STATE_ACCEPT) || (dwDecision == COOKIE_STATE_REJECT)) { retVal = cookieUIhistory.saveDecision( pchHostName, NULL, dwDecision); /* side-effect: choosing to reject all future cookies for a given website implicitly deletes existing cache cookies from that site*/ if (dwDecision==COOKIE_STATE_REJECT) DeletePersistentCookies(pchHostName); } return retVal; } INTERNETAPI_(BOOL) InternetSetPerSiteCookieDecisionW( IN LPCWSTR pwchHostName, DWORD dwDecision) { if( !pwchHostName || IsBadStringPtrW( pwchHostName, INTERNET_MAX_URL_LENGTH)) { SetLastError( ERROR_INVALID_PARAMETER); return FALSE; } MEMORYPACKET mpHostName; ALLOC_MB(pwchHostName,0,mpHostName); if (!mpHostName.psStr) { return FALSE; } UNICODE_TO_ANSI(pwchHostName, mpHostName); return InternetSetPerSiteCookieDecisionA( mpHostName.psStr, dwDecision); } INTERNETAPI_(BOOL) InternetGetPerSiteCookieDecisionA( IN LPCSTR pchHostName, unsigned long* pResult) { if( IsBadWritePtr( pResult, sizeof(unsigned long)) || !pchHostName || IsBadStringPtr(pchHostName, INTERNET_MAX_URL_LENGTH)) { SetLastError( ERROR_INVALID_PARAMETER); return FALSE; } return cookieUIhistory.lookupDecision( pchHostName, NULL, pResult); } INTERNETAPI_(BOOL) InternetGetPerSiteCookieDecisionW( IN LPCWSTR pwchHostName, unsigned long* pResult) { if( IsBadWritePtr( pResult, sizeof(unsigned long)) || !pwchHostName || IsBadStringPtrW(pwchHostName, INTERNET_MAX_URL_LENGTH)) { SetLastError( ERROR_INVALID_PARAMETER); return FALSE; } MEMORYPACKET mpHostName; ALLOC_MB(pwchHostName,0,mpHostName); if (!mpHostName.psStr) { return FALSE; } UNICODE_TO_ANSI(pwchHostName, mpHostName); return InternetGetPerSiteCookieDecisionA( mpHostName.psStr, pResult); } INTERNETAPI_(BOOL) InternetEnumPerSiteCookieDecisionA(OUT LPSTR pszSiteName, IN OUT unsigned long *pcSiteNameSize, OUT unsigned long *pdwDecision, IN unsigned long dwIndex) { int error = ERROR_INVALID_PARAMETER; if( !pcSiteNameSize || IsBadWritePtr( pcSiteNameSize, sizeof(DWORD))) { goto doneInternetEnumPerSiteCookieDecisionA; } if( !pszSiteName || IsBadWritePtr( pszSiteName, *pcSiteNameSize)) { goto doneInternetEnumPerSiteCookieDecisionA; } if( !pdwDecision || IsBadWritePtr(pdwDecision, sizeof(DWORD))) { goto doneInternetEnumPerSiteCookieDecisionA; } error = cookieUIhistory.enumerateDecisions(pszSiteName, pcSiteNameSize, pdwDecision, dwIndex); if( error == ERROR_SUCCESS) *pcSiteNameSize += 1; // reg function doesn't count null terminator in size, it should doneInternetEnumPerSiteCookieDecisionA: if (error!=ERROR_SUCCESS) SetLastError(error); return (error==ERROR_SUCCESS); } INTERNETAPI_(BOOL) InternetEnumPerSiteCookieDecisionW(LPWSTR pwszSiteName, unsigned long *pcSiteNameSize, unsigned long *pdwDecision, unsigned long dwIndex) { DWORD dwErr = ERROR_INVALID_PARAMETER; BOOL fRet = FALSE; LPSTR pszSiteName = NULL; if( !pcSiteNameSize || IsBadWritePtr( pcSiteNameSize, sizeof(DWORD))) goto cleanup; if( !pwszSiteName || IsBadWritePtr( pwszSiteName, *pcSiteNameSize * sizeof(WCHAR))) goto cleanup; if( !pdwDecision || IsBadWritePtr( pdwDecision, sizeof(DWORD))) goto cleanup; pszSiteName = new char[*pcSiteNameSize]; if( !pszSiteName) { dwErr = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } fRet = InternetEnumPerSiteCookieDecisionA( pszSiteName, pcSiteNameSize, pdwDecision, dwIndex); if (fRet) { SHAnsiToUnicode( pszSiteName, pwszSiteName, *pcSiteNameSize); } dwErr = ERROR_SUCCESS; cleanup: if (dwErr!=ERROR_SUCCESS) SetLastError(dwErr); if ( pszSiteName != NULL) delete [] pszSiteName; return fRet; } INTERNETAPI_(BOOL) InternetClearAllPerSiteCookieDecisions() { return cookieUIhistory.clearAll(); } BOOL DeletePersistentCookies(const char *pszDomainSuffix) { BOOL bRetval = TRUE; DWORD dwEntrySize, dwLastEntrySize; LPINTERNET_CACHE_ENTRY_INFOA lpCacheEntry; HANDLE hCacheDir = NULL; dwEntrySize = dwLastEntrySize = MAX_CACHE_ENTRY_INFO_SIZE; lpCacheEntry = (LPINTERNET_CACHE_ENTRY_INFOA) new BYTE[dwEntrySize]; if( lpCacheEntry == NULL) { bRetval = FALSE; goto Exit; } lpCacheEntry->dwStructSize = dwEntrySize; Again: if (!(hCacheDir = FindFirstUrlCacheEntryA("cookie:",lpCacheEntry,&dwEntrySize))) { delete [] lpCacheEntry; switch(GetLastError()) { case ERROR_NO_MORE_ITEMS: goto Exit; case ERROR_INSUFFICIENT_BUFFER: lpCacheEntry = (LPINTERNET_CACHE_ENTRY_INFOA) new BYTE[dwEntrySize]; if( lpCacheEntry == NULL) { bRetval = FALSE; goto Exit; } lpCacheEntry->dwStructSize = dwLastEntrySize = dwEntrySize; goto Again; default: bRetval = FALSE; goto Exit; } } do { if (lpCacheEntry->CacheEntryType & COOKIE_CACHE_ENTRY) { const char achEmpty[] = ""; const char *pszFind = NULL; const char *pszAtSign = strchr(lpCacheEntry->lpszSourceUrlName, '@'); /* The source URL for a cookie has the format: cookie:username@domain/path The logic for determining whether to delete the cookie checks for the following conditions on source URL: 1. presence of the @ sign 2. presence of argument passed in "pszDomainSuffix" as substring 3. the substring must occur after a dot or the @ sign (this avoids partial name matching on domains) 4. substring must occur as suffix, eg only at the end this happens IFF the match is followed by forward slash */ if (pszDomainSuffix==NULL || /* null argument means "delete all cookies" */ (pszAtSign && (pszFind=strstr(pszAtSign+1, pszDomainSuffix)) && (pszFind[-1]=='.' || pszFind[-1]=='@') && pszFind[strlen(pszDomainSuffix)]=='/')) DeleteUrlCacheEntryA(lpCacheEntry->lpszSourceUrlName); } dwEntrySize = dwLastEntrySize; Retry: if (!FindNextUrlCacheEntryA(hCacheDir,lpCacheEntry, &dwEntrySize)) { delete [] lpCacheEntry; switch(GetLastError()) { case ERROR_NO_MORE_ITEMS: goto Exit; case ERROR_INSUFFICIENT_BUFFER: lpCacheEntry = (LPINTERNET_CACHE_ENTRY_INFOA) new BYTE[dwEntrySize]; if( lpCacheEntry == NULL) { bRetval = FALSE; goto Exit; } lpCacheEntry->dwStructSize = dwLastEntrySize = dwEntrySize; goto Retry; default: bRetval = FALSE; goto Exit; } } } while (TRUE); Exit: if (hCacheDir) FindCloseUrlCache(hCacheDir); return bRetval; }