2020-09-30 17:12:32 +02:00

2867 lines
69 KiB
C++

// COOKIE.CXX
// Cookie Jar
// This file implements cookies as defined by Navigator 4 behavior and the
// specification at http://www.netscape.com/newsref/std/cookie_spec.html.
// If Navigator 4 and the specification are not in agreement, we try to
// match the Navigator 4 behavior.
// The following describes some interesting aspects of cookie behavior.
// SYNTAX
// Syntax for cookie is
// [[name]=] value [; options]
// The name is everything before "=" with leading and trailing whitespace
// removed. The value is everything after "=" and before ";" with leading
// and trailing whitespace removed. The name and value can contain spaces,
// quotes or any other character except ";" and "=". The name and equal
// sign are optional.
// Example: =foo -> name: <blank> value: foo
// foo -> name: <blank> value: foo
// foo= -> name: foo value: <blank>
// ; -> name: <blank> value: <blank>
// ORDER
// Cookies with a more specific path are sent before cookies with
// a less specific path mapping. The domain does not contibute
// to the ordering of cookies.
// If the path length of two cookies are equal, then the cookies
// are ordered by time of creation. Navigator maintains this
// ordering across domain and path boundaries. IE maintains this
// ordering for a specific domain and path. It is difficult to match
// the Navigator behavior and there are no known bugs because of
// this difference.
// MATCHING
// Path matches are done at the character level. Any
// directory structure in the path is ignored.
// Navigator matches domains at the character level and ignores
// the structure of the domain name.
// Previous versions of IE tossed the leading "." on a domain name.
// With out this information, character by character compares are
// can produce incorrect results. For backwards compatibilty with
// old cookie we continue to match on a component by component basis.
// Some examples of the difference are:
// Cookie domain Document domain Navigator match IE match
// .foo.com foo.com no yes
// bar.x.com foobar.x.com yes no
// ACCEPTING COOKIES
// A cookie is rejected if the path specified in the set cookie
// header is not a prefix of document's path.
// Navigator rejects a cookie if the domain specified in the
// set cookie header does not contain at least two periods
// or the domain is not a suffix of the document's domain.
// The suffix match is done on a character by character basis.
// Navigator ignores all the stuff in the specification about
// three period matching and the seven special top level domains.
// IE rejects a cookie if the domain specified by the cookie
// header does not contain at least one embedded period or the
// domain is not a suffix of the documents domain.
// Cookies are accepted if the path specified in the set cookie
// header is a prefix of the document's path and the domain
// specified in the set cookie header.
// The difference in behavior is a result of the matching rules
// described in the previous section.
#include <wininetp.h>
#include "httpp.h"
extern DWORD ConfirmCookie(HWND hwnd, HTTP_REQUEST_HANDLE_OBJECT *lpRequest, DWORD dwFlags, LPVOID *lppvData, LPDWORD pdwStopWarning);
#define CCH_COOKIE_MAX (5 * 1024)
CRITICAL_SECTION s_csCookieJar;
static class CCookieJar *s_pJar;
static char s_achEmpty[] = "";
static char s_cCacheModify;
static const char s_achCookieScheme[] = "Cookie:";
static DWORD s_dwCacheVersion;
static BOOL s_fFirstTime = TRUE;
// Hard-coded list of special domains. If any of these are present between the
// second-to-last and last dot we will require 2 embedded dots.
// The domain strings are reversed to make the compares easier
static const char *s_pachSpecialDomains[] =
{"MOC", "UDE", "TEN", "GRO", "VOG", "LIM", "TNI" };
#if INET_DEBUG
DWORD s_dwThreadID;
#endif
// CACHE_ENTRY_INFO_BUFFER
class CACHE_ENTRY_INFO_BUFFER : public INTERNET_CACHE_ENTRY_INFO
{
BYTE _ab[5 * 1024];
};
// CCookieCriticalSection
// Enter / Exit critical section.
class CCookieCriticalSection
{
private:
int Dummy; // Variable needed to force compiler to generate code for const/dest.
public:
CCookieCriticalSection()
{
EnterCriticalSection(&s_csCookieJar);
#if INET_DEBUG
s_dwThreadID = GetCurrentThreadId();
#endif
}
~CCookieCriticalSection()
{
#if INET_DEBUG
s_dwThreadID = 0;
#endif
LeaveCriticalSection(&s_csCookieJar);
}
};
#define ASSERT_CRITSEC() INET_ASSERT(GetCurrentThreadId() == s_dwThreadID)
// CCookieBase
// Provides operator new which allocates extra memory
// after object and initializes the memory to zero.
class CCookieBase
{
public:
void * operator new(size_t cb, size_t cbExtra);
void operator delete(void *pv);
};
// CCookie
// Holds a single cookie value.
class CCookie : public CCookieBase
{
public:
~CCookie();
static CCookie *Construct(const char *pchName);
BOOL SetValue(const char *pchValue);
BOOL WriteCacheFile(HANDLE hFile, char *pchRDomain, char *pchPath);
BOOL CanSend(FILETIME *pftCurrent, BOOL fSecure);
BOOL IsPersistent() { return (_dwFlags & COOKIE_SESSION) == 0; }
BOOL PurgePersistent(void *);
BOOL PurgeSession(void *);
BOOL PurgeAll(void *);
BOOL PurgeByName(void *);
BOOL PurgeThis(void *);
BOOL PurgeExpired(void *);
FILETIME _ftExpire;
FILETIME _ftLastModified;
DWORD _dwFlags;
CCookie * _pCookieNext;
char * _pchName;
char * _pchValue;
};
// CCookieLocation
// Holds all cookies for a given domain and path.
class CCookieLocation : public CCookieBase
{
public:
~CCookieLocation();
static CCookieLocation *Construct(const char *pchRDomain, const char *pchPath);
CCookie * GetCookie(const char *pchName, BOOL fCreate);
BOOL WriteCacheFile();
BOOL ReadCacheFile();
BOOL ReadCacheFileIfNeeded();
BOOL Purge(BOOL (CCookie::*)(void *), void *);
BOOL Purge(FILETIME *pftCurrent, BOOL fSession);
BOOL IsMatch(char *pchRDomain, char *pchPath);
char * GetCacheURL();
FILETIME _ftCacheFileLastModified;
CCookie * _pCookieKids;
CCookieLocation*_pLocationNext;
char * _pchRDomain;
char * _pchPath;
int _cchPath;
BYTE _fCacheFileExists;
BYTE _fReadFromCacheFileNeeded;
};
// CCookieJar
// Maintains fixed size hash table of cookie location objects.
enum SET_COOKIE_RESULT
{
SET_COOKIE_FAIL = 0,
SET_COOKIE_SUCCESS = 1,
SET_COOKIE_DISALLOW = 2,
SET_COOKIE_PENDING = 3
};
class CCookieJar : public CCookieBase
{
public:
static CCookieJar * Construct();
~CCookieJar();
DWORD
OkToSetCookie(
HTTP_REQUEST_HANDLE_OBJECT *pRequest,
const char *pchURL,
char *pchRDomain,
char *pchPath,
char *pchName,
char *pchValue,
DWORD dwFlags,
FILETIME ftExpire
);
void EnforceCookieLimits(CCookieLocation *pLocation, char *pchName, BOOL *pfWriteCacheFileNeeded);
DWORD SetCookie(HTTP_REQUEST_HANDLE_OBJECT *pRequest, const char *pchURL, char *pchHeader, DWORD dwFlags);
void Purge(FILETIME *pftCurrent, BOOL fSession);
BOOL SyncWithCache();
BOOL SyncWithCacheIfNeeded();
void CacheFilesModified();
CCookieLocation** GetBucket(const char *pchRDomain);
CCookieLocation * GetLocation(const char *pchRDomain, const char *pchPath, BOOL fCreate);
CCookieLocation * _apLocation[128];
};
// Track cache modificaitons.
inline void
MarkCacheModified()
{
IncrementUrlCacheHeaderData(CACHE_HEADER_DATA_COOKIE_CHANGE_COUNT, &s_dwCacheVersion);
}
inline BOOL
IsCacheModified()
{
DWORD dwCacheVersion;
if (s_fFirstTime)
{
s_fFirstTime = FALSE;
GetUrlCacheHeaderData(CACHE_HEADER_DATA_COOKIE_CHANGE_COUNT, &s_dwCacheVersion);
return TRUE;
}
else
{
dwCacheVersion = s_dwCacheVersion;
GetUrlCacheHeaderData(CACHE_HEADER_DATA_COOKIE_CHANGE_COUNT, &s_dwCacheVersion);
return dwCacheVersion != s_dwCacheVersion;
}
}
// String utilities
static BOOL
IsZero(FILETIME *pft)
{
return pft->dwLowDateTime == 0 && pft->dwHighDateTime == 0;
}
static char *
StrnDup(const char *pch, int cch)
{
char *pchAlloc = (char *)ALLOCATE_MEMORY(LMEM_FIXED, cch + 1);
if (!pchAlloc)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return NULL;
}
memcpy(pchAlloc, pch, cch);
pchAlloc[cch] = 0;
return pchAlloc;
}
static BOOL
IsPathMatch(const char *pchPrefix, const char *pchStr)
{
while (*pchPrefix == *pchStr && *pchStr)
{
pchPrefix += 1;
pchStr += 1;
}
return *pchPrefix == 0;
}
static BOOL
IsDomainMatch(const char *pchPrefix, const char *pchStr)
{
while (*pchPrefix == *pchStr && *pchStr)
{
pchPrefix += 1;
pchStr += 1;
}
return *pchPrefix == 0 && (*pchStr == 0 || *pchStr == '.');
}
static BOOL
IsPathLegal(const char *pchHeader, const char *pchDocument)
{
return TRUE;
/*
We attempted to implement the specification here.
It looks like Navigator does not reject cookies
based on the path attribute. We now consider
all path attributes to be legal.
while (*pchHeader == *pchDocument && *pchDocument)
{
pchHeader += 1;
pchDocument += 1;
}
if (*pchDocument == 0)
{
while (*pchHeader && *pchHeader != '/' && *pchHeader != '\\')
{
pchHeader += 1;
}
}
return *pchHeader == 0;
*/
}
static BOOL
IsSpecialDomain(const char *pch, int nCount)
{
// Currently all the special strings are exactly 3 characters long.
if (pch == NULL || nCount != 3)
return FALSE;
for (int i = 0 ; i < ARRAY_ELEMENTS(s_pachSpecialDomains) ; i++ )
{
if (StrCmpNIC(pch, s_pachSpecialDomains[i], nCount) == 0)
return TRUE;
}
return FALSE;
}
static BOOL
IsDomainLegal(const char *pchHeader, const char *pchDocument)
{
const char *pch = pchHeader;
int c = 0;
int d = 0;
int rgcch[2] = { 0, 0 }; // How many characters between dots
// Must have at least one period in name.
// and contains nothing but '.' is illegal
int countChars = 0;
const char * pchSecondPart = NULL; // for a domain string such as
for (; *pch; pch++)
{
if (*pch == '.')
{
if ( c < 2 )
{
// Remember how many characters we have between the last two dots
// For example if domain header is .microsoft.com
// rgcch[0] should be 3 for "com"
// rgcch[1] should be 9 for "microsoft"
rgcch[c] = countChars;
if (c == 1)
{
pchSecondPart = pch - countChars;
}
}
countChars = 0;
c += 1;
}
else
{
countChars++;
}
d++;
}
// The code below depends on the leading dot being removed from the domain header.
// The parse code does that, but an assert here just in case something changes in the
// parse code.
INET_ASSERT(*(pch - 1) != '.');
// Remember the count of the characters between the begining of the header and
// the first dot. So for domain=abc.com this will set rgch[1] = 3.
// Note that this assumes that if domain=.abc.com the leading dot has been stripped
// out in the parse code. See assert above.
if (c < 2 )
{
rgcch[c] = countChars;
if (c == 1)
{
pchSecondPart = pch - countChars;
}
}
// If the domain name is of the form abc.xx.yy where the number of characters between the last two dots is less than
// 2 we require a minimum of two embedded dots. This is so you are not allowed to set cookies readable by all of .co.nz for
// example. However this rule is not sufficient and we special case things like .edu.nz as well.
int cEmbeddedDotsNeeded = 1;
if (rgcch[0] <= 2)
{
if (rgcch[1] <= 2 || (pchSecondPart && IsSpecialDomain(pchSecondPart, rgcch[1])))
cEmbeddedDotsNeeded = 2;
}
if (c < cEmbeddedDotsNeeded || d == c)
return FALSE;
// Mismatch between header and document not allowed.
// Must match full components of domain name.
while (*pchHeader == *pchDocument && *pchDocument)
{
pchHeader += 1;
pchDocument += 1;
}
return *pchHeader == 0 && (*pchDocument == 0 || *pchDocument == '.' );
}
void
LowerCaseString(char *pch)
{
for (; *pch; pch++)
{
if (*pch >= 'A' && *pch <= 'Z')
*pch += 'a' - 'A';
}
}
static void
ReverseString(char *pchFront)
{
char *pchBack;
char ch;
int cch;
cch = strlen(pchFront);
pchBack = pchFront + cch - 1;
cch = cch / 2;
while (--cch >= 0)
{
ch = *pchFront;
*pchFront = *pchBack;
*pchBack = ch;
pchFront += 1;
pchBack -= 1;
}
}
static BOOL
PathAndRDomainFromURL(const char *pchURL, char **ppchRDomain, char **ppchPath, BOOL *pfSecure, BOOL bStrip = TRUE)
{
char *pchDomainBuf;
char *pchRDomain = NULL;
char *pchPathBuf;
char *pchPath = NULL;
char *pchExtra;
DWORD cchDomain;
DWORD cchPath;
DWORD cchExtra;
BOOL fSuccess = FALSE;
DWORD dwError;
INTERNET_SCHEME ustScheme;
char *pchURLCopy = NULL;
pchURLCopy = NewString(pchURL);
if (!pchURLCopy)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
goto Cleanup;
}
dwError = CrackUrl((char *)pchURLCopy,
0,
FALSE,
&ustScheme,
NULL, // Scheme Name
NULL, // Scheme Lenth
&pchDomainBuf,
&cchDomain,
NULL, // Internet Port
NULL, // UserName
NULL, // UserName Length
NULL, // Password
NULL, // Password Lenth
&pchPathBuf,
&cchPath,
&pchExtra, // Extra
&cchExtra, // Extra Length
NULL);
if (dwError != ERROR_SUCCESS)
{
SetLastError(dwError);
goto Cleanup;
}
if ( ustScheme != INTERNET_SCHEME_HTTP &&
ustScheme != INTERNET_SCHEME_HTTPS &&
ustScheme != INTERNET_SCHEME_FILE)
{
SetLastError(ERROR_INVALID_PARAMETER);
goto Cleanup;
}
*pfSecure = ustScheme == INTERNET_SCHEME_HTTPS;
if (ustScheme == INTERNET_SCHEME_FILE)
{
pchDomainBuf = "~~local~~";
cchDomain = sizeof("~~local~~") - 1;
}
else
{
// SECURITY: It's possible for us to navigate to a carefully
// constructed URL such as http://server%3F.microsoft.com.
// This results in a cracked hostname of server?.microsoft.com.
// Given the current architecture, it would probably be best to
// make CrackUrl smarter. However, the minimal fix below prevents
// the x-domain security violation without breaking escaped cases
// that work today that customers may expect to be allowed.
DWORD n;
for (n = 0; n < cchDomain; n++)
{
// RFC 952 as amended by RFC 1123: the only valid chars are
// a-z, A-Z, 0-9, '-', and '.'. The last two are delimiters
// which cannot start or end the name, but this detail doesn't
// matter for the security fix.
if (!((pchDomainBuf[n] >= 'a' && pchDomainBuf[n] <= 'z') ||
(pchDomainBuf[n] >= 'A' && pchDomainBuf[n] <= 'Z') ||
(pchDomainBuf[n] >= '0' && pchDomainBuf[n] <= '9') ||
(pchDomainBuf[n] == '-') ||
(pchDomainBuf[n] == '.')))
{
// REVIEW
// What if this was incorrectly cracked and the escaped
// path, query string, or fragment was incorrectly dumped
// here? Do we care and/or want to support this case?
// After all, we could get CrackUrl to recognize %2f characters
// for slashes, or we could reconstruct the path here if
// a lower impact is desired...
pchDomainBuf[n] = '\0';
cchDomain = n;
break;
}
}
}
if(bStrip)
{
while (cchPath > 0)
{
if (pchPathBuf[cchPath - 1] == '/' || pchPathBuf[cchPath - 1] == '\\')
{
break;
}
cchPath -= 1;
}
}
pchRDomain = StrnDup(pchDomainBuf, cchDomain);
if (!pchRDomain)
goto Cleanup;
LowerCaseString(pchRDomain);
ReverseString(pchRDomain);
pchPath = (char *)ALLOCATE_MEMORY(LMEM_FIXED, cchPath + 2);
if (!pchPath)
goto Cleanup;
if (*pchPathBuf != '/')
{
*pchPath = '/';
memcpy(pchPath + 1, pchPathBuf, cchPath);
pchPath[cchPath + 1] = 0;
}
else
{
memcpy(pchPath, pchPathBuf, cchPath);
pchPath[cchPath] = 0;
}
fSuccess = TRUE;
Cleanup:
if (!fSuccess)
{
if (pchRDomain)
FREE_MEMORY(pchRDomain);
if (pchPath)
FREE_MEMORY(pchPath);
}
else
{
*ppchRDomain = pchRDomain;
*ppchPath = pchPath;
}
if (pchURLCopy)
FREE_MEMORY(pchURLCopy);
return fSuccess;
}
// CCookieBase implementation
void *
CCookieBase::operator new(size_t cb, size_t cbExtra)
{
void *pv = ALLOCATE_MEMORY(LMEM_FIXED, cb + cbExtra);
if (!pv)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return NULL;
}
memset(pv, 0, cb);
return pv;
}
inline void
CCookieBase::operator delete(void *pv)
{
FREE_MEMORY(pv);
}
// CCookie implementation
CCookie *
CCookie::Construct(const char *pchName)
{
CCookie *pCookie = new(strlen(pchName) + 1) CCookie();
if (!pCookie)
return NULL;
pCookie->_pchName = (char *)(pCookie + 1);
pCookie->_pchValue = s_achEmpty;
strcpy(pCookie->_pchName, pchName);
pCookie->_dwFlags = COOKIE_SESSION;
return pCookie;
}
CCookie::~CCookie()
{
if (_pchValue != s_achEmpty)
FREE_MEMORY(_pchValue);
}
BOOL
CCookie::SetValue(const char *pchValue)
{
int cchValue;
if (_pchValue != s_achEmpty)
FREE_MEMORY(_pchValue);
if (!pchValue || !*pchValue)
{
_pchValue = s_achEmpty;
}
else
{
cchValue = strlen(pchValue) + 1;
_pchValue = (char *)ALLOCATE_MEMORY(LMEM_FIXED, cchValue);
if (!_pchValue)
{
_pchValue = s_achEmpty;
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return FALSE;
}
memcpy(_pchValue, pchValue, cchValue);
}
return TRUE;
}
BOOL
CCookie::CanSend(FILETIME *pftCurrent, BOOL fSecure)
{
return (fSecure || !(_dwFlags & COOKIE_SECURE)) &&
(CompareFileTime(_ftExpire, *pftCurrent) >= 0);
}
BOOL CCookie::PurgePersistent(void *)
{
return IsPersistent();
}
BOOL CCookie::PurgeAll(void *)
{
return TRUE;
}
BOOL CCookie::PurgeByName(void *pvName)
{
return strcmp((char *)pvName, _pchName) == 0;
}
BOOL CCookie::PurgeThis(void *pvThis)
{
return this == (CCookie *)pvThis;
}
BOOL CCookie::PurgeExpired(void *pvCurrent)
{
return CompareFileTime(_ftExpire, *(FILETIME *)pvCurrent) < 0;
}
static BOOL
WriteString(HANDLE hFile, const char *pch)
{
DWORD cb;
return pch && *pch ? WriteFile(hFile, pch, strlen(pch), &cb, NULL) : TRUE;
}
static BOOL
WriteStringLF(HANDLE hFile, const char *pch)
{
DWORD cb;
if (!WriteString(hFile, pch)) return FALSE;
return WriteFile(hFile, "\n", 1, &cb, NULL);
}
BOOL
CCookie::WriteCacheFile(HANDLE hFile, char *pchRDomain, char *pchPath)
{
BOOL fSuccess = FALSE;
char achBuf[128];
ReverseString(pchRDomain);
if (!WriteStringLF(hFile, _pchName)) goto Cleanup;
if (!WriteStringLF(hFile, _pchValue)) goto Cleanup;
if (!WriteString(hFile, pchRDomain)) goto Cleanup;
if (!WriteStringLF(hFile, pchPath)) goto Cleanup;
wsprintf(achBuf, "%u\n%u\n%u\n%u\n%u\n*\n",
_dwFlags,
_ftExpire.dwLowDateTime,
_ftExpire.dwHighDateTime,
_ftLastModified.dwLowDateTime,
_ftLastModified.dwHighDateTime);
if (!WriteString(hFile, achBuf)) goto Cleanup;
fSuccess = TRUE;
Cleanup:
ReverseString(pchRDomain);
return fSuccess;
}
// CCookieLocation implementation
CCookieLocation *
CCookieLocation::Construct(const char *pchRDomain, const char *pchPath)
{
int cchPath = strlen(pchPath);
CCookieLocation *pLocation = new(strlen(pchRDomain) + cchPath + 2) CCookieLocation();
if (!pLocation)
return NULL;
pLocation->_cchPath = cchPath;
pLocation->_pchPath = (char *)(pLocation + 1);
pLocation->_pchRDomain = pLocation->_pchPath + cchPath + 1;
strcpy(pLocation->_pchRDomain, pchRDomain);
strcpy(pLocation->_pchPath, pchPath);
return pLocation;
}
CCookieLocation::~CCookieLocation()
{
Purge(CCookie::PurgeAll, NULL);
}
CCookie *
CCookieLocation::GetCookie(const char *pchName, BOOL fCreate)
{
CCookie *pCookie;
CCookie **ppCookie = &_pCookieKids;
for (pCookie = _pCookieKids; pCookie; pCookie = pCookie->_pCookieNext)
{
if (strcmp(pchName, pCookie->_pchName) == 0)
return pCookie;
ppCookie = &pCookie->_pCookieNext;
}
if (!fCreate)
return NULL;
pCookie = CCookie::Construct(pchName);
if (!pCookie)
return NULL;
// Insert cookie at end of list to match Navigator's behavior.
pCookie->_pCookieNext = NULL;
*ppCookie = pCookie;
return pCookie;
}
BOOL
CCookieLocation::Purge(BOOL (CCookie::*pfnPurge)(void *), void *pv)
{
CCookie **ppCookie = &_pCookieKids;
CCookie *pCookie;
BOOL fPersistentDeleted = FALSE;
while ((pCookie = *ppCookie) != NULL)
{
if ((pCookie->*pfnPurge)(pv))
{
*ppCookie = pCookie->_pCookieNext;
fPersistentDeleted |= pCookie->IsPersistent();
delete pCookie;
}
else
{
ppCookie = &pCookie->_pCookieNext;
}
}
return fPersistentDeleted;
}
BOOL
CCookieLocation::Purge(FILETIME *pftCurrent, BOOL fSession)
{
if (!_fCacheFileExists)
{
// If cache file is gone, then delete all persistent
// cookies. If there's no cache file, then it's certainly
// the case that we do not need to read the cache file.
Purge(CCookie::PurgePersistent, NULL);
_fReadFromCacheFileNeeded = FALSE;
}
// This is a good time to check for expired persistent cookies.
if (!_fReadFromCacheFileNeeded && Purge(CCookie::PurgeExpired, pftCurrent))
{
WriteCacheFile();
}
if (fSession)
{
// If we are purging because a session ended, nuke
// everything in sight. If we deleted a persistent
// cookie, note that we need to read the cache file
// on next access.
_fReadFromCacheFileNeeded |= Purge(CCookie::PurgeAll, NULL);
}
return !_fReadFromCacheFileNeeded && _pCookieKids == NULL;
}
char *
CCookieLocation::GetCacheURL()
{
char *pchURL;
char *pch;
int cchScheme = sizeof(s_achCookieScheme) - 1;
int cchUser = vdwCurrentUserLen;
int cchAt = 1;
int cchDomain = strlen(_pchRDomain);
int cchPath = strlen(_pchPath);
pchURL = (char *)ALLOCATE_MEMORY(LMEM_FIXED, cchScheme + cchUser + cchAt + cchDomain + cchPath + 1);
if (!pchURL)
return NULL;
pch = pchURL;
memcpy(pch, s_achCookieScheme, cchScheme);
pch += cchScheme;
memcpy(pch, vszCurrentUser, cchUser);
pch += cchUser;
memcpy(pch, "@", cchAt);
pch += cchAt;
ReverseString(_pchRDomain);
memcpy(pch, _pchRDomain, cchDomain);
ReverseString(_pchRDomain);
pch += cchDomain;
strcpy(pch, _pchPath);
return pchURL;
}
BOOL
CCookieLocation::WriteCacheFile()
{
HANDLE hFile = INVALID_HANDLE_VALUE;
char achFile[MAX_PATH];
char * pchURL = NULL;
BOOL fSuccess = FALSE;
CCookie * pCookie;
FILETIME ftLastExpire = { 0, 0 };
achFile[0] = 0;
GetCurrentGmtTime(&_ftCacheFileLastModified);
// Determine the latest expiry time and if we have something to write.
for (pCookie = _pCookieKids; pCookie; pCookie = pCookie->_pCookieNext)
{
if (pCookie->IsPersistent() && CompareFileTime(pCookie->_ftExpire, ftLastExpire) > 0)
{
ftLastExpire = pCookie->_ftExpire;
}
}
pchURL = GetCacheURL();
if (!pchURL)
goto Cleanup;
if (CompareFileTime(ftLastExpire, _ftCacheFileLastModified) < 0)
{
fSuccess = TRUE;
DeleteUrlCacheEntry(pchURL);
_fCacheFileExists = FALSE;
goto Cleanup;
}
_fCacheFileExists = TRUE;
if (!CreateUrlCacheEntry(pchURL,
0, // Estimated size
"txt", // File extension
achFile,
0))
goto Cleanup;
hFile = CreateFile(
achFile,
GENERIC_WRITE,
0, // no sharing.
NULL,
TRUNCATE_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL );
if (hFile == INVALID_HANDLE_VALUE)
goto Cleanup;
for (pCookie = _pCookieKids; pCookie; pCookie = pCookie->_pCookieNext)
{
if (pCookie->IsPersistent() && CompareFileTime(pCookie->_ftExpire, _ftCacheFileLastModified) >= 0)
{
if (!pCookie->WriteCacheFile(hFile, _pchRDomain, _pchPath))
goto Cleanup;
}
}
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
if (!CommitUrlCacheEntry(pchURL,
achFile,
ftLastExpire,
_ftCacheFileLastModified,
NORMAL_CACHE_ENTRY | COOKIE_CACHE_ENTRY,
NULL,
0,
NULL,
0 ))
goto Cleanup;
MarkCacheModified();
fSuccess = TRUE;
Cleanup:
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
if (!fSuccess)
{
if (achFile[0])
DeleteFile(achFile);
if (pchURL)
DeleteUrlCacheEntry(pchURL);
}
if (pchURL)
FREE_MEMORY(pchURL);
return fSuccess;
}
static char *
ScanString(char *pch, char **pchStr)
{
*pchStr = pch;
for (; *pch; *pch++)
{
if (*pch == '\n')
{
*pch = 0;
pch += 1;
break;
}
}
return pch;
}
static char *
ScanNumber(char *pch, DWORD *pdw)
{
DWORD dw = 0;
char *pchJunk;
for (; *pch >= '0' && *pch <= '9'; *pch++)
{
dw = (dw * 10) + *pch - '0';
}
*pdw = dw;
return ScanString(pch, &pchJunk);
}
BOOL
CCookieLocation::ReadCacheFile()
{
char * pchURL = NULL;
char * pch;
DWORD cbCEI;
HANDLE hCacheStream = NULL;
char * pchBuffer = NULL;
CCookie * pCookie;
CACHE_ENTRY_INFO_BUFFER cei;
_fReadFromCacheFileNeeded = FALSE;
pchURL = GetCacheURL();
if (!pchURL)
goto Cleanup;
cbCEI = sizeof(cei);
hCacheStream = RetrieveUrlCacheEntryStream(
pchURL,
&cei,
&cbCEI,
FALSE, // sequential access
0);
if (!hCacheStream)
{
// If we failed to get the entry, try to nuke it so it does not
// bother us in the future.
DeleteUrlCacheEntry(pchURL);
goto Cleanup;
}
// Old cache files to not have last modified time set.
// Bump the time up so that we can use file times to determine
// if we need to resync a file.
if (IsZero(&cei.LastModifiedTime))
{
cei.LastModifiedTime.dwLowDateTime = 1;
}
_ftCacheFileLastModified = cei.LastModifiedTime;
// Read cache file into a null terminated buffer.
pchBuffer = (char *)ALLOCATE_MEMORY(LMEM_FIXED, cei.dwSizeLow + 1 * sizeof(char));
if (!pchBuffer)
{
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
goto Cleanup;
}
if (!ReadUrlCacheEntryStream(hCacheStream, 0, pchBuffer, &cei.dwSizeLow, 0))
goto Cleanup;
pchBuffer[cei.dwSizeLow] = 0;
// Blow away all existing persistent cookies.
Purge(CCookie::PurgePersistent, NULL);
// Parse cookies from the buffer;
for (pch = pchBuffer; *pch; )
{
char *pchName;
char *pchValue;
char *pchLocation;
char *pchStar;
DWORD dwFlags;
FILETIME ftExpire;
FILETIME ftLast;
pch = ScanString(pch, &pchName);
pch = ScanString(pch, &pchValue);
pch = ScanString(pch, &pchLocation);
pch = ScanNumber(pch, &dwFlags);
pch = ScanNumber(pch, &ftExpire.dwLowDateTime);
pch = ScanNumber(pch, &ftExpire.dwHighDateTime);
pch = ScanNumber(pch, &ftLast.dwLowDateTime);
pch = ScanNumber(pch, &ftLast.dwHighDateTime);
pch = ScanString(pch, &pchStar);
if (strcmp(pchStar, "*"))
{
goto Cleanup;
}
pCookie = GetCookie(pchName, TRUE);
if (!pCookie)
goto Cleanup;
// Initialize the cookie.
pCookie->SetValue(pchValue);
pCookie->_ftExpire = ftExpire;
pCookie->_ftLastModified = ftLast;
pCookie->_dwFlags = dwFlags;
}
Cleanup:
if (hCacheStream)
UnlockUrlCacheEntryStream(hCacheStream, 0);
if (pchURL)
FREE_MEMORY(pchURL);
if (pchBuffer)
FREE_MEMORY(pchBuffer);
return TRUE;
}
BOOL
CCookieLocation::IsMatch(char *pchRDomain, char *pchPath)
{
return IsDomainMatch(_pchRDomain, pchRDomain) &&
IsPathMatch(_pchPath, pchPath);
}
BOOL
CCookieLocation::ReadCacheFileIfNeeded()
{
return _fReadFromCacheFileNeeded ? ReadCacheFile() : TRUE;
}
// CCookieJar implementation
CCookieJar *
CCookieJar::Construct()
{
CCookieJar *s_pJar = new(0) CCookieJar();
if (!s_pJar)
return NULL;
return s_pJar;
}
CCookieJar::~CCookieJar()
{
for (int i = ARRAY_ELEMENTS(_apLocation); --i >= 0; )
{
CCookieLocation *pLocation = _apLocation[i];
while (pLocation)
{
CCookieLocation *pLocationT = pLocation->_pLocationNext;
delete pLocation;
pLocation = pLocationT;
}
}
}
CCookieLocation **
CCookieJar::GetBucket(const char *pchRDomain)
{
int ch;
int cPeriod = 0;
unsigned int hash = 0;
ASSERT_CRITSEC();
for (; (ch = *pchRDomain) != 0; pchRDomain++)
{
if (ch == '.')
{
cPeriod += 1;
if (cPeriod >= 2)
break;
}
hash = (hash * 29) + ch;
}
hash = hash % ARRAY_ELEMENTS(_apLocation);
return &_apLocation[hash];
}
CCookieLocation *
CCookieJar::GetLocation(const char *pchRDomain, const char *pchPath, BOOL fCreate)
{
ASSERT_CRITSEC();
int cchPath = strlen(pchPath);
CCookieLocation *pLocation = NULL;
CCookieLocation **ppLocation = GetBucket(pchRDomain);
// To support sending more specific cookies before less specific,
// we keep list sorted by path length.
while ((pLocation = *ppLocation) != NULL)
{
if (pLocation->_cchPath < cchPath)
break;
if (strcmp(pLocation->_pchPath, pchPath) == 0 &&
strcmp(pLocation->_pchRDomain, pchRDomain) == 0)
return pLocation;
ppLocation = &pLocation->_pLocationNext;
}
if (!fCreate)
goto Cleanup;
pLocation = CCookieLocation::Construct(pchRDomain, pchPath);
if (!pLocation)
goto Cleanup;
pLocation->_pLocationNext = *ppLocation;
*ppLocation = pLocation;
Cleanup:
return pLocation;
}
void
CCookieJar::Purge(FILETIME *pftCurrent, BOOL fSession)
{
ASSERT_CRITSEC();
for (int i = ARRAY_ELEMENTS(_apLocation); --i >= 0; )
{
CCookieLocation **ppLocation = &_apLocation[i];
CCookieLocation *pLocation;
while ((pLocation = *ppLocation) != NULL)
{
if (pLocation->Purge(pftCurrent, fSession))
{
*ppLocation = pLocation->_pLocationNext;
delete pLocation;
}
else
{
ppLocation = &pLocation->_pLocationNext;
}
}
}
}
BOOL
CCookieJar::SyncWithCache()
{
DWORD dwBufferSize;
HANDLE hEnum = NULL;
int cchUserNameAt;
char achUserNameAt[MAX_PATH + 2];
FILETIME ftCurrent;
char * pchRDomain;
char * pchPath;
char * pch;
CACHE_ENTRY_INFO_BUFFER cei;
CCookieLocation *pLocation;
ASSERT_CRITSEC();
if (!vdwCurrentUserLen)
GetWininetUserName();
strcpy(achUserNameAt, vszCurrentUser);
strcat(achUserNameAt, "@");
cchUserNameAt = vdwCurrentUserLen+1;
dwBufferSize = sizeof(cei);
hEnum = FindFirstUrlCacheEntry(s_achCookieScheme, &cei, &dwBufferSize);
for (int i = ARRAY_ELEMENTS(_apLocation); --i >= 0; )
{
for (pLocation = _apLocation[i];
pLocation;
pLocation = pLocation->_pLocationNext)
{
pLocation->_fCacheFileExists = FALSE;
}
}
if (hEnum)
{
do
{
if ( cei.lpszSourceUrlName &&
(strnicmp(cei.lpszSourceUrlName, s_achCookieScheme, sizeof(s_achCookieScheme) - 1 ) == 0) &&
(strnicmp(cei.lpszSourceUrlName+sizeof(s_achCookieScheme) - 1,achUserNameAt, cchUserNameAt) == 0))
{
// Split domain name from path in buffer.
// Slide domain name down to make space for null terminator
// between domain and path.
pchRDomain = cei.lpszSourceUrlName+sizeof(s_achCookieScheme) - 1 + cchUserNameAt - 1;
for (pch = pchRDomain + 1; *pch && *pch != '/'; pch++)
{
pch[-1] = pch[0];
}
pch[-1] = 0;
pchPath = pch;
ReverseString(pchRDomain);
pLocation = GetLocation(pchRDomain, pchPath, TRUE);
if (!pLocation)
{
continue;
}
// Old cache files to not have last modified time set.
// Bump the time up so that we can use file times to determine
// if we need to resync a file.
if (IsZero(&cei.LastModifiedTime))
{
cei.LastModifiedTime.dwLowDateTime = 1;
}
if (CompareFileTime(pLocation->_ftCacheFileLastModified, cei.LastModifiedTime) < 0)
{
pLocation->_fReadFromCacheFileNeeded = TRUE;
}
pLocation->_fCacheFileExists = TRUE;
}
dwBufferSize = sizeof(cei);
} while (FindNextUrlCacheEntryA(hEnum, &cei, &dwBufferSize));
FindCloseUrlCache(hEnum);
}
// Now purge everthing we didn't get .
GetCurrentGmtTime(&ftCurrent);
Purge(&ftCurrent, FALSE);
return TRUE;
}
BOOL
CCookieJar::SyncWithCacheIfNeeded()
{
return IsCacheModified() ? SyncWithCache() : TRUE;
}
struct PARSE
{
char *pchBuffer;
char *pchToken;
BOOL fEqualFound;
};
static char *
SkipWS(char *pch)
{
while (*pch == ' ' || *pch == '\t')
pch += 1;
return pch;
}
static BOOL
ParseToken(PARSE *pParse, BOOL fBreakOnSpecialTokens, BOOL fBreakOnEqual)
{
char ch;
char *pch;
char *pchEndToken;
pParse->fEqualFound = FALSE;
pch = SkipWS(pParse->pchBuffer);
if (*pch == 0)
{
pParse->pchToken = pch;
return FALSE;
}
pParse->pchToken = pch;
pchEndToken = pch;
while ((ch = *pch) != 0)
{
pch += 1;
if (ch == ';')
{
break;
}
else if (fBreakOnEqual && ch == '=')
{
pParse->fEqualFound = TRUE;
break;
}
else if (ch == ' ' || ch == '\t')
{
if (fBreakOnSpecialTokens)
{
if ((strnicmp(pch, "expires", sizeof("expires") - 1) == 0) ||
(strnicmp(pch, "path", sizeof("path") - 1) == 0) ||
(strnicmp(pch, "domain", sizeof("domain") - 1) == 0) ||
(strnicmp(pch, "secure", sizeof("secure") - 1) == 0))
{
break;
}
}
}
else
{
pchEndToken = pch;
}
}
*pchEndToken = 0;
pParse->pchBuffer = pch;
return TRUE;
}
static void
ParseHeader(
char *pchHeader,
char **ppchName,
char **ppchValue,
char **ppchPath,
char **ppchRDomain,
DWORD *pdwFlags,
FILETIME *pftExpire)
{
PARSE parse;
parse.pchBuffer = pchHeader;
*ppchName = NULL;
*ppchValue = NULL;
*ppchPath = NULL;
*ppchRDomain = NULL;
*pdwFlags = COOKIE_SESSION;
// If only one of name or value is specified, Navigator
// uses name=<blank> and value as what ever was specified.
// Example: =foo -> name: <blank> value: foo
// foo -> name: <blank> value: foo
// foo= -> name: foo value: <blank>
if (ParseToken(&parse, FALSE, TRUE))
{
*ppchName = parse.pchToken;
if (parse.fEqualFound)
{
if (ParseToken(&parse, FALSE, FALSE))
{
*ppchValue = parse.pchToken;
}
else
{
*ppchValue = s_achEmpty;
}
}
else
{
*ppchValue = *ppchName;
*ppchName = s_achEmpty;
}
}
while (ParseToken(&parse, FALSE, TRUE))
{
if (stricmp(parse.pchToken, "expires") == 0)
{
if (parse.fEqualFound && ParseToken(&parse, TRUE, FALSE))
{
FParseHttpDate(pftExpire, parse.pchToken);
*pdwFlags &= ~COOKIE_SESSION;
}
}
else if (stricmp(parse.pchToken, "domain") == 0)
{
if (parse.fEqualFound )
{
if( ParseToken(&parse, TRUE, FALSE))
{
// Previous versions of IE tossed the leading
// "." on domain names. We continue this behavior
// to maintain compatiblity with old cookie files.
// See comments at the top of this file for more
// information.
if (*parse.pchToken == '.') parse.pchToken += 1;
ReverseString(parse.pchToken);
*ppchRDomain = parse.pchToken;
}
else
{
*ppchRDomain = parse.pchToken;
}
}
}
else if (stricmp(parse.pchToken, "path") == 0)
{
if (parse.fEqualFound && ParseToken(&parse, TRUE, FALSE))
{
*ppchPath = parse.pchToken;
}
else
{
*ppchPath = s_achEmpty;
}
}
else if (stricmp(parse.pchToken, "secure") == 0)
{
*pdwFlags |= COOKIE_SECURE;
if (parse.fEqualFound)
{
ParseToken(&parse, TRUE, FALSE);
}
}
else
{
if (parse.fEqualFound)
{
ParseToken(&parse, TRUE, FALSE);
}
}
}
if (!*ppchName)
{
*ppchName = *ppchValue = s_achEmpty;
}
if (*pdwFlags & COOKIE_SESSION)
{
pftExpire->dwLowDateTime = 0xFFFFFFFF;
pftExpire->dwHighDateTime = 0x7FFFFFFF;
}
}
// free's an INTERNET_COOKIE structure
static VOID
DestroyInternetCookie(INTERNET_COOKIE *pic)
{
if ( pic != NULL )
{
if ( pic->pszDomain ) {
FREE_MEMORY(pic->pszDomain);
}
if ( pic->pszPath ) {
FREE_MEMORY(pic->pszPath);
}
if ( pic->pszName ) {
FREE_MEMORY(pic->pszName);
}
if ( pic->pszData ) {
FREE_MEMORY(pic->pszData);
}
if ( pic->pszUrl ) {
FREE_MEMORY(pic->pszUrl);
}
if( pic->pftExpires ) {
delete pic->pftExpires;
pic->pftExpires = NULL;
}
FREE_MEMORY(pic);
}
}
// allocate's an INTERNET_COOKIE structure
static INTERNET_COOKIE *
MakeInternetCookie(
const char *pchURL,
char *pchRDomain,
char *pchPath,
char *pchName,
char *pchValue,
DWORD dwFlags,
FILETIME ftExpire
)
{
INTERNET_COOKIE *pic = NULL;
pic = (INTERNET_COOKIE *) ALLOCATE_MEMORY(LMEM_ZEROINIT, sizeof(INTERNET_COOKIE));
if ( pic == NULL ) {
return NULL;
}
pic->cbSize = sizeof(INTERNET_COOKIE);
pic->pszDomain = pchRDomain ? NewString(pchRDomain) : NULL;
if (pic->pszDomain) {
ReverseString(pic->pszDomain);
}
pic->pszPath = pchPath ? NewString(pchPath) : NULL;
pic->pszName = pchName ? NewString(pchName) : NULL;
pic->pszData = pchValue ? NewString(pchValue) : NULL;
pic->pszUrl = pchURL ? NewString(pchURL) : NULL;
#if COOKIE_SECURE != INTERNET_COOKIE_IS_SECURE
#error MakeInternetCookie depends on cookie flags to remain the same
#endif
pic->dwFlags = dwFlags;
if( dwFlags & COOKIE_SESSION )
{
pic->pftExpires = NULL;
}
else
{
pic->pftExpires = new FILETIME;
if( pic->pftExpires )
{
memcpy(pic->pftExpires, &ftExpire, sizeof(FILETIME));
}
}
return pic;
}
DWORD
CCookieJar::OkToSetCookie(
HTTP_REQUEST_HANDLE_OBJECT *pRequest,
const char *pchURL,
char *pchRDomain,
char *pchPath,
char *pchName,
char *pchValue,
DWORD dwFlags,
FILETIME ftExpire
)
{
DWORD dwError;
DWORD dwCookiesPolicy;
BOOL fCleanupPic = FALSE;
INTERNET_COOKIE *pic = NULL, *pic_result;
// Deal first with the basic quick cases to determine if we need UI here.
// they are:
// - Do we require UI or cookies to be set all, for the current Url Zone?
// - Do we allow UI for this given request?
if( pchURL )
{
dwCookiesPolicy = ::GetCookiePolicy(pchURL, dwFlags & COOKIE_SESSION);
}
else
{
return SET_COOKIE_FAIL;
}
if( dwCookiesPolicy == URLPOLICY_ALLOW
|| (dwCookiesPolicy == URLPOLICY_QUERY && (dwFlags & COOKIE_NOUI) != 0))
{
return SET_COOKIE_SUCCESS;
}
else if( dwCookiesPolicy == URLPOLICY_DISALLOW )
{
return SET_COOKIE_DISALLOW;
}
if (pRequest && (pRequest->GetOpenFlags() & INTERNET_FLAG_NO_UI))
{
return SET_COOKIE_FAIL;
}
// Now look up the cookie and confirm that it hasn't just been added,
// if its already added to the Cookie list, then we don't show UI,
// since once the user has chosen to add a given Cookie, we don't repeatly re-prompt
{
CCookieCriticalSection cs;
CCookieLocation *pLocation;
if (!SyncWithCacheIfNeeded())
return SET_COOKIE_FAIL;
pLocation = GetLocation(pchRDomain, pchPath, FALSE /* no creation*/);
if (pLocation)
{
CCookie *pCookie;
pLocation->ReadCacheFileIfNeeded();
pCookie = pLocation->GetCookie(pchName, FALSE /* no creation */);
if (pCookie) {
return SET_COOKIE_SUCCESS;
}
}
}
// Now make the async request, to see if we can put up UI
{
DWORD dwStopWarning = 0;
DWORD dwAction;
DWORD dwResult;
pic = MakeInternetCookie(pchURL,
pchRDomain,
pchPath,
pchName,
pchValue,
dwFlags,
ftExpire
);
if ( pic == NULL ) {
return SET_COOKIE_FAIL;
}
fCleanupPic = TRUE;
pic_result = pic;
dwError = ChangeUIBlockingState(
(HINTERNET) pRequest,
ERROR_HTTP_COOKIE_NEEDS_CONFIRMATION,
&dwAction,
&dwResult,
(LPVOID *) &pic_result
);
switch (dwAction)
{
case UI_ACTION_CODE_NONE_TAKEN:
// fallback to old behavior
dwError = ConfirmCookie(NULL, pRequest, 0, (void **)&pic, &dwStopWarning);
if( dwStopWarning )
{
// call zone mgr to report user want stop warning...
::SetStopWarning(pchURL, URLPOLICY_ALLOW, dwFlags & COOKIE_SESSION);
}
break;
case UI_ACTION_CODE_USER_ACTION_COMPLETED:
// UI has been completed, cleanup, and then continue
if ( pic_result && (pic_result != pic) ) {
DestroyInternetCookie(pic_result);
}
INET_ASSERT(fCleanupPic);
dwError = dwResult;
break;
case UI_ACTION_CODE_BLOCKED_FOR_USER_INPUT:
// Go pending while we wait for the UI to be ours.
INET_ASSERT(pic_result == pic);
fCleanupPic = FALSE; // the UI needs this info, don't delete
// fall through ...
case UI_ACTION_CODE_BLOCKED_FOR_INTERNET_HANDLE:
INET_ASSERT(dwError == ERROR_IO_PENDING);
break;
}
}
#if 0
else
{
BOOL fRet = FALSE;
LPSTR lpszMsgBuf = NULL;
LPSTR lplpArr[3];
char buff[INTERNET_RFC1123_BUFSIZE], msg[MAX_PATH], titlebuff[MAX_PATH];
SYSTEMTIME sSysTime;
if (!LoadString(GlobalDllHandle, IDS_SETTING_COOKIE, msg, sizeof(msg))) {
return SET_COOKIE_FAIL;
}
if (!LoadString(GlobalDllHandle, IDS_SETTING_COOKIE_TITLE, titlebuff, sizeof(titlebuff))) {
titlebuff[0] = 0;
}
buff[0] = 0;
// get a prettified time
if (FileTimeToSystemTime(&ftExpire, &sSysTime) &&
InternetTimeFromSystemTime(&sSysTime, INTERNET_RFC1123_FORMAT, buff, INTERNET_RFC1123_BUFSIZE)){
}
ReverseString(pchRDomain);
lplpArr[0] = pchRDomain;
lplpArr[1] = (pchValue)?pchValue:"";
lplpArr[2] = buff;
if(FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
msg,
IDS_SETTING_COOKIE,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpszMsgBuf,
0,
(va_list *)lplpArr)) { // BUGBUG Cast for ALPHA, dont know if it will work
fRet = (MessageBox( NULL, (LPCSTR)lpszMsgBuf, (LPCSTR)titlebuff, MB_ICONQUESTION|MB_YESNO|MB_SETFOREGROUND|MB_TOPMOST) == IDYES);
FREE_MEMORY(lpszMsgBuf);
}
else {
dwError = GetLastError();
}
ReverseString(pchRDomain);
if( fRet )
return SET_COOKIE_SUCCESS;
else
return SET_COOKIE_FAIL;
}
#endif
if ( fCleanupPic )
{
DestroyInternetCookie(pic);
}
if (dwError != ERROR_SUCCESS)
{
SetLastError(dwError);
if ( dwError == ERROR_IO_PENDING ) {
return SET_COOKIE_PENDING;
} else {
return SET_COOKIE_FAIL;
}
}
else
{
return SET_COOKIE_SUCCESS;
}
}
DWORD
CCookieJar::SetCookie(HTTP_REQUEST_HANDLE_OBJECT *pRequest, const char *pchURL, char *pchHeader, DWORD dwFlags = 0)
{
FILETIME ftExpire;
FILETIME ftCurrent;
char *pchName;
char *pchValue;
char *pchHeaderPath;
char *pchHeaderRDomain;
char *pchDocumentRDomain = NULL;
char *pchDocumentPath = NULL;
DWORD dwFlagsFromParse;
BOOL fDocumentSecure;
BOOL fDelete;
DWORD dwRet = SET_COOKIE_FAIL;
BOOL fWriteToCacheFileNeeded;
CCookieLocation *pLocation;
ParseHeader(pchHeader, &pchName, &pchValue, &pchHeaderPath, &pchHeaderRDomain, &dwFlagsFromParse, &ftExpire);
// merge flags given with those found by the parser.
dwFlags |= dwFlagsFromParse;
if (!PathAndRDomainFromURL(pchURL, &pchDocumentRDomain, &pchDocumentPath, &fDocumentSecure))
goto Cleanup;
// Verify domain and path
if ((pchHeaderRDomain && !IsDomainLegal(pchHeaderRDomain, pchDocumentRDomain)) ||
(pchHeaderPath && !IsPathLegal(pchHeaderPath, pchDocumentPath)))
{
SetLastError(ERROR_INVALID_PARAMETER);
goto Cleanup;
}
if (!pchHeaderRDomain)
pchHeaderRDomain = pchDocumentRDomain;
if (!pchHeaderPath)
pchHeaderPath = pchDocumentPath;
// Delete the cookie?
GetCurrentGmtTime(&ftCurrent);
fDelete = CompareFileTime(ftCurrent, ftExpire) > 0;
// Ok to add?
if (!fDelete) {
dwRet = OkToSetCookie(
pRequest,
pchURL,
pchHeaderRDomain,
pchHeaderPath,
pchName,
pchValue,
dwFlags,
ftExpire);
if (dwRet != SET_COOKIE_SUCCESS )
goto Cleanup;
}
// Finally, we can add the cookie!
{
CCookieCriticalSection cs;
if (!SyncWithCacheIfNeeded())
goto Cleanup;
pLocation = GetLocation(pchHeaderRDomain, pchHeaderPath, !fDelete);
if (pLocation)
{
pLocation->ReadCacheFileIfNeeded();
fWriteToCacheFileNeeded = FALSE;
if (fDelete)
{
fWriteToCacheFileNeeded |= pLocation->Purge(CCookie::PurgeByName, pchName);
}
else
{
CCookie *pCookie;
EnforceCookieLimits(pLocation, pchName, &fWriteToCacheFileNeeded);
pCookie = pLocation->GetCookie(pchName, TRUE);
if (!pCookie)
goto Cleanup;
pCookie->_ftLastModified = ftCurrent;
if (memcmp(&ftExpire, &pCookie->_ftExpire, sizeof(FILETIME)) ||
strcmp(pchValue, pCookie->_pchValue) ||
dwFlags != pCookie->_dwFlags)
{
fWriteToCacheFileNeeded |= pCookie->IsPersistent();
pCookie->_ftExpire = ftExpire;
pCookie->_dwFlags = dwFlags;
pCookie->SetValue(pchValue);
fWriteToCacheFileNeeded |= pCookie->IsPersistent();
}
}
if (fWriteToCacheFileNeeded)
{
if (!pLocation->WriteCacheFile())
goto Cleanup;
}
}
}
dwRet = SET_COOKIE_SUCCESS;
Cleanup:
if (pchDocumentRDomain)
FREE_MEMORY(pchDocumentRDomain);
if (pchDocumentPath)
FREE_MEMORY(pchDocumentPath);
return dwRet;
}
void
CCookieJar::EnforceCookieLimits(CCookieLocation *pLocationNew, char *pchNameNew, BOOL *fWriteToCacheFileNeeded)
{
CCookieLocation *pLocation;
CCookieLocation *pLocationVictim;
CCookie *pCookie;
CCookie *pCookieVictim = NULL;
int nCookie = 0;
for (pLocation = *GetBucket(pLocationNew->_pchRDomain); pLocation; pLocation = pLocation->_pLocationNext)
{
// Same domain?
if (stricmp(pLocationNew->_pchRDomain, pLocation->_pchRDomain) == 0)
{
pLocation->ReadCacheFileIfNeeded();
for (pCookie = pLocation->_pCookieKids; pCookie; pCookie = pCookie->_pCookieNext)
{
nCookie += 1;
if (pLocation == pLocationNew && strcmp(pCookie->_pchName, pchNameNew) == 0)
{
// No need to enforce limits when resetting existing cookie value.
return;
}
if (!pCookieVictim ||
CompareFileTime(pCookie->_ftLastModified, pCookieVictim->_ftLastModified) < 0)
{
pCookieVictim = pCookie;
pLocationVictim = pLocation;
}
}
}
}
if (nCookie >= 20)
{
INET_ASSERT(pCookieVictim != NULL && pLocationVictim != NULL);
if (pLocationVictim->Purge(CCookie::PurgeThis, pCookieVictim))
{
pLocationVictim->WriteCacheFile();
}
}
}
// External APIs
BOOL
OpenTheCookieJar()
{
if (s_pJar)
return TRUE;
s_pJar = CCookieJar::Construct();
if (!s_pJar)
return FALSE;
InitializeCriticalSection(&s_csCookieJar);
return TRUE;
}
void
CloseTheCookieJar()
{
if (s_pJar)
{
DeleteCriticalSection(&s_csCookieJar);
delete s_pJar;
}
s_pJar = NULL;
}
void
PurgeCookieJarOfStaleCookies()
{
FILETIME ftCurrent;
if (s_pJar)
{
CCookieCriticalSection cs;
GetCurrentGmtTime(&ftCurrent);
s_pJar->Purge(&ftCurrent, TRUE);
}
}
INTERNETAPI BOOL WINAPI
InternetGetCookieW(
LPCWSTR lpszUrl,
LPCWSTR lpszCookieName,
LPWSTR lpszCookieData,
LPDWORD lpdwSize
)
{
DEBUG_ENTER_API((DBG_INET,
Bool,
"InternetGetCookieW",
"%wq, %#x, %#x, %#x",
lpszUrl,
lpszCookieName,
lpszCookieData,
lpdwSize
));
DWORD dwErr = ERROR_SUCCESS;
BOOL fResult = FALSE;
MEMORYPACKET mpUrl, mpCookieName, mpCookieData;
ALLOC_MB(lpszUrl,0,mpUrl);
if (!mpUrl.psStr)
{
dwErr = ERROR_NOT_ENOUGH_MEMORY;
goto cleanup;
}
UNICODE_TO_ANSI(lpszUrl,mpUrl);
if (lpszCookieName)
{
ALLOC_MB(lpszCookieName,0,mpCookieName);
if (!mpCookieName.psStr)
{
dwErr = ERROR_NOT_ENOUGH_MEMORY;
goto cleanup;
}
UNICODE_TO_ANSI(lpszCookieName,mpCookieName);
}
if (lpszCookieData)
{
mpCookieData.dwAlloc = mpCookieData.dwSize = *lpdwSize;
mpCookieData.psStr = (LPSTR)ALLOC_BYTES(*lpdwSize);
if (!mpCookieData.psStr)
{
dwErr = ERROR_NOT_ENOUGH_MEMORY;
goto cleanup;
}
}
fResult = InternetGetCookieA(mpUrl.psStr, mpCookieName.psStr, mpCookieData.psStr, &mpCookieData.dwSize);
*lpdwSize = mpCookieData.dwSize*sizeof(WCHAR);
if (lpszCookieData)
{
if (mpCookieData.dwSize <= mpCookieData.dwAlloc)
{
MAYBE_COPY_ANSI(mpCookieData,lpszCookieData,*lpdwSize);
}
else
{
dwErr = ERROR_INSUFFICIENT_BUFFER;
fResult = FALSE;
}
}
cleanup:
if (dwErr!=ERROR_SUCCESS)
{
SetLastError(dwErr);
DEBUG_ERROR(INET, dwErr);
}
DEBUG_LEAVE_API(fResult);
return fResult;
}
// InternetGetCookieEx only returns those cookies within domain pchURL
//with a name that maches pchCookieName
BOOL InternetGetCookieEx(
IN LPCSTR pchURL,
IN LPCSTR pchCookieName OPTIONAL,
IN LPSTR pchCookieData OPTIONAL,
IN OUT LPDWORD pcchCookieData,
IN DWORD dwFlags,
IN LPVOID lpReserved)
{
DEBUG_ENTER_API((DBG_INET,
Bool,
"InternetGetCookieA",
"%q, %#x, %#x, %#x",
pchURL,
pchCookieName,
pchCookieData,
pcchCookieData
));
// force everyone to not give anything in lpReserved
INET_ASSERT( lpReserved == NULL);
if( lpReserved != NULL)
{
DEBUG_LEAVE_API(FALSE);
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
// force everyone to not give anything in dwFlags
INET_ASSERT( dwFlags == 0);
if( dwFlags != 0)
{
DEBUG_LEAVE_API(FALSE);
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
BOOL fSuccess = FALSE;
char * pchRDomain = NULL;
char * pchPath = NULL;
BOOL fSecure;
DWORD cch = 0;
BOOL fFirst;
int cchName;
int cchValue;
FILETIME ftCurrent;
CCookieLocation *pLocation;
CCookie *pCookie;
DWORD dwErr = ERROR_SUCCESS;
if (!pcchCookieData || !pchURL)
{
dwErr = ERROR_INVALID_PARAMETER;
goto done;
}
if (!GlobalDataInitialized) {
dwErr = GlobalDataInitialize();
if (dwErr!= ERROR_SUCCESS) {
goto done;
}
}
// NOTE THIS SEEMS TO BE A BUG BUG BUG
if (!PathAndRDomainFromURL(pchURL, &pchRDomain, &pchPath, &fSecure))
goto Cleanup;
fFirst = TRUE;
GetCurrentGmtTime(&ftCurrent);
{
CCookieCriticalSection cs;
if (!s_pJar->SyncWithCacheIfNeeded())
goto Cleanup;
for (pLocation = *s_pJar->GetBucket(pchRDomain); pLocation; pLocation = pLocation->_pLocationNext)
{
if (pLocation->IsMatch(pchRDomain, pchPath))
{
pLocation->ReadCacheFileIfNeeded();
for (pCookie = pLocation->_pCookieKids; pCookie; pCookie = pCookie->_pCookieNext)
{
BOOL fAllow = TRUE;
if (::GetCookiePolicy(
pchURL,
!(pCookie->IsPersistent())
) == URLPOLICY_DISALLOW
)
{
fAllow = FALSE;
}
if (fAllow
&& pCookie->CanSend(&ftCurrent, fSecure)
&& (pchCookieName == NULL
|| StrCmp( pCookie->_pchName, pchCookieName) == 0))
{
if (!fFirst) cch += 2; // for ; <space>
cch += cchName = strlen(pCookie->_pchName);
cch += cchValue = strlen(pCookie->_pchValue);
if (cchName && cchValue) cch += 1; // for equal sign
if (pchCookieData && cch < *pcchCookieData)
{
if (!fFirst)
{
*pchCookieData++ = ';';
*pchCookieData++ = ' ';
}
if (cchName > 0)
{
memcpy(pchCookieData, pCookie->_pchName, cchName);
pchCookieData += cchName;
if (cchValue > 0)
{
*pchCookieData++ = '=';
}
}
if (cchValue > 0)
{
memcpy(pchCookieData, pCookie->_pchValue, cchValue);
pchCookieData += cchValue;
}
}
fFirst = FALSE;
}
}
}
}
}
//TerminateBuffer:
cch += 1;
if (pchCookieData)
{
if (cch > *pcchCookieData)
{
dwErr = ERROR_INSUFFICIENT_BUFFER;
}
else
{
*pchCookieData = 0;
fSuccess = TRUE;
}
}
else
{
fSuccess = TRUE;
}
if (cch == 1)
{
dwErr = ERROR_NO_MORE_ITEMS;
fSuccess = FALSE;
cch = 0;
}
*pcchCookieData = cch;
Cleanup:
if (pchRDomain)
FREE_MEMORY(pchRDomain);
if (pchPath)
FREE_MEMORY(pchPath);
done:
if (dwErr!=ERROR_SUCCESS)
{
SetLastError(dwErr);
DEBUG_ERROR(INET, dwErr);
}
DEBUG_LEAVE_API(fSuccess);
return fSuccess;
}
INTERNETAPI BOOL WINAPI
InternetGetCookieA(
IN LPCSTR pchURL,
IN LPCSTR pchCookieName OPTIONAL,
IN LPSTR pchCookieData OPTIONAL,
IN OUT LPDWORD pcchCookieData
)
{
// Because the value in pchCookieName had no effect on
//the previously exported API, Ex gets NULL to ensure
//the behavior doesn't change.
return InternetGetCookieEx( pchURL, NULL, pchCookieData,
pcchCookieData, 0, NULL);
}
INTERNETAPI BOOL WINAPI
InternetSetCookieW(
LPCWSTR lpszUrl,
LPCWSTR lpszCookieName,
LPCWSTR lpszCookieData)
{
DEBUG_ENTER_API((DBG_INET,
Bool,
"InternetSetCookieW",
"%wq, %#x, %#x",
lpszUrl,
lpszCookieName,
lpszCookieData
));
DWORD dwErr = ERROR_SUCCESS;
BOOL fResult = FALSE;
MEMORYPACKET mpUrl, mpCookieName, mpCookieData;
if (lpszUrl)
{
ALLOC_MB(lpszUrl,0,mpUrl);
if (!mpUrl.psStr)
{
dwErr = ERROR_NOT_ENOUGH_MEMORY;
goto cleanup;
}
UNICODE_TO_ANSI(lpszUrl,mpUrl);
}
if (lpszCookieName)
{
ALLOC_MB(lpszCookieName,0,mpCookieName);
if (!mpCookieName.psStr)
{
dwErr = ERROR_NOT_ENOUGH_MEMORY;
goto cleanup;
}
UNICODE_TO_ANSI(lpszCookieName,mpCookieName);
}
if (lpszCookieData)
{
ALLOC_MB(lpszCookieData,0,mpCookieData);
if (!mpCookieData.psStr)
{
dwErr = ERROR_NOT_ENOUGH_MEMORY;
goto cleanup;
}
UNICODE_TO_ANSI(lpszCookieData,mpCookieData);
}
fResult = InternetSetCookieA(mpUrl.psStr, mpCookieName.psStr, mpCookieData.psStr);
cleanup:
if (dwErr!=ERROR_SUCCESS)
{
SetLastError(dwErr);
DEBUG_ERROR(INET, dwErr);
}
DEBUG_LEAVE_API(fResult);
return fResult;
}
BOOL InternetSetCookieEx(
LPCSTR pchURL,
LPCSTR pchCookieName,
LPCSTR pchCookieData,
DWORD dwFlags,
LPVOID lpReserved
)
{
DEBUG_ENTER_API((DBG_INET,
Bool,
"InternetSetCookieA",
"%q, %#x, %#x",
pchURL,
pchCookieName,
pchCookieData
));
// force everyone to not give anything in lpReserved
INET_ASSERT( lpReserved == NULL);
if( lpReserved != NULL)
{
DEBUG_LEAVE_API(FALSE);
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
int cch;
char * pch;
char achHeader[CCH_COOKIE_MAX];
int cchT;
DWORD dwErr = ERROR_SUCCESS;
BOOL fResult = TRUE;
if (!pchURL || !pchCookieData)
{
fResult = FALSE;
dwErr = ERROR_INVALID_PARAMETER;
goto done;
}
if (!GlobalDataInitialized) {
dwErr = GlobalDataInitialize();
if (dwErr!= ERROR_SUCCESS) {
fResult = FALSE;
goto done;
}
}
cch = CCH_COOKIE_MAX - 2; // one for null terminator, one for "="
pch = achHeader;
if (pchCookieName)
{
cchT = strlen(pchCookieName);
if (cchT > cch)
cchT = cch;
memcpy(pch, pchCookieName, cchT);
pch += cchT;
cch -= cchT;
memcpy(pch, "=", 1);
pch += 1;
cch -= 1;
}
// Ensure null termination upon overflow.
if (cch <= 0)
cch = 1;
// Append the cookie data.
lstrcpyn (pch, pchCookieData, cch);
if(s_pJar->SetCookie(NULL, pchURL, achHeader, dwFlags) == SET_COOKIE_FAIL )
{
fResult = FALSE;
}
done:
if (dwErr!=ERROR_SUCCESS)
{
SetLastError(dwErr);
DEBUG_ERROR(INET, dwErr);
}
DEBUG_LEAVE_API(fResult);
return fResult;
}
INTERNETAPI BOOL WINAPI
InternetSetCookieA(
LPCSTR pchURL,
LPCSTR pchCookieName,
LPCSTR pchCookieData
)
{
return InternetSetCookieEx( pchURL, pchCookieName, pchCookieData, 0, NULL);
}
// rambling comments, delete before checkin...
// returns struc, and pending, error
// on subsequent attempts passes back, with index, or index incremented
// perhaps can store index in fsm, and the rest in UI
// need to handle multi dlgs, perhaps via checking added Cookie.
DWORD
HTTP_REQUEST_HANDLE_OBJECT::ExtractSetCookieHeaders(LPDWORD lpdwHeaderIndex)
{
char achHeader[CCH_COOKIE_MAX];
DWORD cbHeader;
DWORD iQuery = 0;
int cCookies = 0;
DWORD error = ERROR_HTTP_COOKIE_DECLINED;
INET_ASSERT(lpdwHeaderIndex);
cbHeader = sizeof(achHeader) - 1;
_ResponseHeaders.LockHeaders();
iQuery = *lpdwHeaderIndex;
while ( QueryResponseHeader(
HTTP_QUERY_SET_COOKIE,
achHeader,
&cbHeader,
0,
&iQuery) == ERROR_SUCCESS)
{
achHeader[cbHeader] = 0;
DWORD dwRet = s_pJar->SetCookie(this, GetURL(), achHeader);
if (dwRet == SET_COOKIE_SUCCESS)
{
cCookies += 1;
*lpdwHeaderIndex = iQuery;
error = ERROR_SUCCESS;
}
else if (dwRet == SET_COOKIE_PENDING )
{
error = ERROR_IO_PENDING;
INET_ASSERT(iQuery != 0);
*lpdwHeaderIndex = iQuery - 1; // back up and retry this cookie
break;
}
cbHeader = sizeof(achHeader) - 1;
}
_ResponseHeaders.UnlockHeaders();
return error;
}
int
HTTP_REQUEST_HANDLE_OBJECT::CreateCookieHeaderIfNeeded(VOID)
{
int cCookie = 0;
char * pchRDomain = NULL;
char * pchPath = NULL;
BOOL fSecure;
DWORD cch;
int cchName;
int cchValue;
char * pchHeader;
char achHeader[CCH_COOKIE_MAX];
FILETIME ftCurrent;
CCookieLocation *pLocation;
CCookie *pCookie;
// remove cookie header if it exists
// BUGBUG - we are overriding the app. Original cookie code has this. Don't know why.
ReplaceRequestHeader(HTTP_QUERY_COOKIE, NULL, 0, 0, 0);
if (!PathAndRDomainFromURL(GetURL(), &pchRDomain, &pchPath, &fSecure, FALSE))
goto Cleanup;
fSecure = GetOpenFlags() & INTERNET_FLAG_SECURE;
GetCurrentGmtTime(&ftCurrent);
{
CCookieCriticalSection cs;
if (!s_pJar->SyncWithCacheIfNeeded())
goto Cleanup;
LockHeaders();
for (pLocation = *s_pJar->GetBucket(pchRDomain); pLocation; pLocation = pLocation->_pLocationNext)
{
if (pLocation->IsMatch(pchRDomain, pchPath))
{
pLocation->ReadCacheFileIfNeeded();
for (pCookie = pLocation->_pCookieKids; pCookie; pCookie = pCookie->_pCookieNext)
{
if (pCookie->CanSend(&ftCurrent, fSecure))
{
pchHeader = achHeader;
cch = 0;
cch += cchName = strlen(pCookie->_pchName);
cch += cchValue = strlen(pCookie->_pchValue);
if (cchName && cchValue) cch += 1; // for equal sign
if (cch < CCH_COOKIE_MAX)
{
if (cchName > 0)
{
memcpy(pchHeader, pCookie->_pchName, cchName);
pchHeader += cchName;
if (cchValue > 0)
{
*pchHeader++ = '=';
}
}
if (cchValue > 0)
{
memcpy(pchHeader, pCookie->_pchValue, cchValue);
pchHeader += cchValue;
}
cCookie += 1;
if( ::GetCookiePolicy(
GetURL(),
!(pCookie->IsPersistent())
) != URLPOLICY_DISALLOW
)
{
AddRequestHeader(HTTP_QUERY_COOKIE,
achHeader,
cch,
0,
HTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON);
}
}
} // if CanSend
} // for pCookie
} // if IsMatch
} // for
UnlockHeaders();
}
Cleanup:
if (pchRDomain)
FREE_MEMORY(pchRDomain);
if (pchPath)
FREE_MEMORY(pchPath);
return cCookie;
}