You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
623 lines
18 KiB
623 lines
18 KiB
|
|
|
|
#include <wininetp.h>
|
|
|
|
#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;
|
|
}
|