//--------------------------------------------------------------------------- // // 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: value: foo // foo -> name: value: foo // foo= -> name: foo value: // ; -> name: value: // // 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 #include "httpp.h" #include "cookiepolicy.h" #include "cookieprompt.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; // Registry path for prompt-history static const char regpathPromptHistory[] = INTERNET_SETTINGS_KEY "\\P3P\\History"; // Prompt history-- persists user decisions about cookies CCookiePromptHistory cookieUIhistory(regpathPromptHistory); // 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_pachSpecialRestrictedDomains[] = {"MOC", "UDE", "TEN", "GRO", "VOG", "LIM", "TNI" }; static const char s_chSpecialAllowedDomains[] = "vt."; // domains ending with ".tv" always only need one dot /* Non-scriptable cookies */ #define COOKIE_NONSCRIPTABLE 0x00002000 const char gcszNoScriptField[] = "httponly"; #if INET_DEBUG DWORD s_dwThreadID; #endif // values returned from cookie UI #define COOKIE_DONT_ALLOW 1 #define COOKIE_ALLOW 2 #define COOKIE_ALLOW_ALL 4 #define COOKIE_DONT_ALLOW_ALL 8 // Function declaration BOOL EvaluateCookiePolicy(const char *pszURL, BOOL f3rdParty, BOOL fRestricted, P3PCookieState *pState, const char *pszHostName=NULL); DWORD getImpliedCookieFlags(P3PCookieState *pState); DWORD GetCookieMainSwitch(DWORD dwZone); DWORD GetCookieMainSwitch(LPCSTR pszURL); #define IsLegacyCookie(pc) ((pc->_dwFlags & INTERNET_COOKIE_IE6) == 0) //--------------------------------------------------------------------------- // // 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 IsRestricted() { return (_dwFlags & COOKIE_RESTRICT)!= 0; } BOOL IsLegacy() { return (_dwFlags & INTERNET_COOKIE_IS_LEGACY) != 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; DWORD _dwPromptMask; // never persisted, only used in session }; //--------------------------------------------------------------------------- // // 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 COOKIE_RESULT { COOKIE_FAIL = 0, COOKIE_SUCCESS = 1, COOKIE_DISALLOW = 2, COOKIE_PENDING = 3 }; class CCookieJar : public CCookieBase { public: static CCookieJar * Construct(); ~CCookieJar(); struct CookieInfo { const char *pchURL; char *pchRDomain; char *pchPath; char *pchName; char *pchValue; DWORD dwFlags; FILETIME ftExpire; P3PCookieState *pP3PState; }; DWORD CheckCookiePolicy( HTTP_REQUEST_HANDLE_OBJECT *pRequest, CookieInfo *pInfo, DWORD dwOperation ); void EnforceCookieLimits(CCookieLocation *pLocation, char *pchName, BOOL *pfWriteCacheFileNeeded); DWORD SetCookie(HTTP_REQUEST_HANDLE_OBJECT *pRequest, const char *pchURL, char *pchHeader, DWORD &dwFlags, P3PCookieState *pState, LPDWORD pdwAction); 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; */ } // // DarrenMi: No longer need IsVerySpecialDomain extern PTSTR GlobalSpecialDomains; extern PTSTR *GlobalSDOffsets; static BOOL IsVerySpecialDomain(const char *pch, int nTopLevel, int nSecond) { if (!GlobalSpecialDomains) { HKEY hk; if (ERROR_SUCCESS==RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\5.0"), 0, KEY_READ, &hk)) { DWORD dwType, dwSize; if ((ERROR_SUCCESS==RegQueryValueEx(hk, "SpecialDomains", NULL, &dwType, NULL, &dwSize)) && (REG_SZ==dwType)) { GlobalSpecialDomains = new TCHAR[dwSize]; if (GlobalSpecialDomains && (ERROR_SUCCESS==RegQueryValueEx(hk, "SpecialDomains", NULL, &dwType, (LPBYTE)GlobalSpecialDomains, &dwSize))) { // We're going to scan a string stored in the registry to gather the domains we should // allow. Format: // [domain] [domain] [domain] // The delimiter is a space character. PTSTR psz = GlobalSpecialDomains; DWORD dwDomains = 0; BOOL fWord = FALSE; while (*psz) { if (*psz==TEXT(' ')) { if (fWord) { fWord = FALSE; dwDomains++; } } else { fWord = TRUE; } psz++; } if (fWord) { dwDomains++; } GlobalSDOffsets = (PTSTR*)new PTSTR[dwDomains+1]; if (GlobalSDOffsets) { psz = GlobalSpecialDomains; for (DWORD dw = 0; dw < dwDomains; dw++) { INET_ASSERT(*psz); while (*psz==TEXT(' ')) psz++; INET_ASSERT(*psz); GlobalSDOffsets[dw] = psz; while (*psz && *psz!=TEXT(' ')) { psz++; } if (*psz) { *psz = TEXT('\0'); psz++; } } GlobalSDOffsets[dwDomains] = NULL; } } } RegCloseKey(hk); } } // WARNING: The following lines of code make it possible for cookies to be set for *.uk, // (for example) if "ku." is in the registry BOOL fRet = FALSE; if (GlobalSDOffsets) { for (DWORD i = 0; GlobalSDOffsets[i]; i++) { if (!StrCmpNI(pch, GlobalSDOffsets[i], nTopLevel) || !StrCmpNI(pch, GlobalSDOffsets[i], nTopLevel+nSecond+1)) { fRet = TRUE; break; } } } return fRet; } static BOOL IsSpecialDomain(const char *pch, int nCount) { for (int i = 0 ; i < ARRAY_ELEMENTS(s_pachSpecialRestrictedDomains) ; i++ ) { if (StrCmpNIC(pch, s_pachSpecialRestrictedDomains[i], nCount) == 0) return TRUE; } return FALSE; } static BOOL IsDomainLegal(const char *pchHeader, const char *pchDocument) { const char *pchCurrent = pchHeader; int nSegment = 0; int dwCharCount = 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 dwSegmentLength = 0; const char * pchSecondPart = NULL; // for a domain string such as BOOL fIPAddress = TRUE; for (; *pchCurrent; pchCurrent++) { if (isalpha(*pchCurrent)) { fIPAddress = FALSE; } if (*pchCurrent == '.') { if (nSegment < 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[nSegment] = dwSegmentLength; if (nSegment == 1) { pchSecondPart = pchCurrent - dwSegmentLength; } } dwSegmentLength = 0; nSegment += 1; } else { dwSegmentLength++; } dwCharCount++; } // 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(*(pchCurrent - 1) != '.'); if (fIPAddress) { // If we're given an IP address, we must match the entire IP address, not just a part while (*pchHeader == *pchDocument && *pchDocument) { pchHeader++; pchDocument++; } return !(*pchHeader || *pchDocument); } // 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 (nSegment < 2 ) { rgcch[nSegment] = dwSegmentLength; if (nSegment==1) { pchSecondPart = pchCurrent - dwSegmentLength; } } // 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. // darrenmi: new semantics: if segment 0 is less than or equal to 2, you need 2 embedded dots if segment 1 is a // "well known" string including edu, com, etc. and co. // fschwiet: An exception is now being made so that domains of the form "??.tv" are allowed. int cEmbeddedDotsNeeded = 1; BOOL fIsVerySpecialDomain = FALSE; if (rgcch[0] <= 2 && rgcch[1] <= 2) { fIsVerySpecialDomain = IsVerySpecialDomain(pchHeader, rgcch[0], rgcch[1]); } if(!fIsVerySpecialDomain && rgcch[0] <= 2 && 0 != StrCmpNIC( pchHeader, s_chSpecialAllowedDomains, sizeof( s_chSpecialAllowedDomains) - 1) && (rgcch[1] <= 2 || IsSpecialDomain(pchSecondPart, rgcch[1]))) { cEmbeddedDotsNeeded = 2; } if (nSegment < cEmbeddedDotsNeeded || dwCharCount == nSegment) 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 = tolower(*pchFront); *pchFront = tolower(*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) { // // known scheme should be supported // e.g. 3rd party pluggable protocol should be able to // call cookie api to setup cookies... // //a-thkesa. Allowing all of the pluggable protocols to setcookie creates security concerns. //So we only allow this "hcp" to qualify to setup cookie apart from 'file', Http, Https. //WinSE BUG: 24011 . In The windows help system they are setting cookie on a HCP protocol!. //we don't expect any protocol other then HTTP, HTTPS ,and File setting cookie here. But //we also have to allow the pluggable protocols to set cookie. Since allowing that causes security //problem, we only allow HCP which is a pluggable protocol used in windows Help and Support. //In future, we have to make sure to allow all of the pluggable protocol to set cookie, or //document only http, https and file can set cookies! //pluggable protocols returns INTERNET_SCHEME_UNKNOWN. //If so check if it returns INTERNET_SCHEME_UNKNOWN and check the protocols is a HCP protocol. If it is HCP // then do not set error. if( INTERNET_SCHEME_UNKNOWN == ustScheme ) // HCP returns INTERNET_SCHEME_UNKNOWN { char szProtocolU[]= "HCP:"; char szProtocolL[]= "hcp:"; short ilen = 0; while(4 > ilen) // check only the first four char. { if(*(pchURLCopy+ilen) != *(szProtocolU+ilen) && *(pchURLCopy+ilen) != *(szProtocolL+ilen) ) { SetLastError(ERROR_INVALID_PARAMETER); goto Cleanup; } ilen++; } } else { 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] == '.'))) { // Since we're incorrectly cracking the URL, don't worry // about fixing up the path. fSuccess = FALSE; goto Cleanup; } } } 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] = TEXT('\0'); } else { memcpy(pchPath, pchPathBuf, cchPath); pchPath[cchPath] = TEXT('\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; pCookie->_dwPromptMask = 0; 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 *pcei = new CACHE_ENTRY_INFO_BUFFER; if (pcei == NULL) goto Cleanup; _fReadFromCacheFileNeeded = FALSE; pchURL = GetCacheURL(); if (!pchURL) goto Cleanup; cbCEI = sizeof(*pcei); hCacheStream = RetrieveUrlCacheEntryStream( pchURL, pcei, &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(&(pcei->LastModifiedTime))) { pcei->LastModifiedTime.dwLowDateTime = 1; } _ftCacheFileLastModified = pcei->LastModifiedTime; // Read cache file into a null terminated buffer. pchBuffer = (char *)ALLOCATE_MEMORY(LMEM_FIXED, pcei->dwSizeLow + 1 * sizeof(char)); if (!pchBuffer) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); goto Cleanup; } if (!ReadUrlCacheEntryStream(hCacheStream, 0, pchBuffer, &pcei->dwSizeLow, 0)) goto Cleanup; pchBuffer[pcei->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 (pcei) delete pcei; 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 *pcei; CCookieLocation *pLocation; ASSERT_CRITSEC(); pcei = new CACHE_ENTRY_INFO_BUFFER; if (pcei == NULL) goto Cleanup; if (!vdwCurrentUserLen) GetWininetUserName(); strcpy(achUserNameAt, vszCurrentUser); strcat(achUserNameAt, "@"); cchUserNameAt = vdwCurrentUserLen+1; dwBufferSize = sizeof(*pcei); hEnum = FindFirstUrlCacheEntry(s_achCookieScheme, pcei, &dwBufferSize); for (int i = ARRAY_ELEMENTS(_apLocation); --i >= 0; ) { for (pLocation = _apLocation[i]; pLocation; pLocation = pLocation->_pLocationNext) { pLocation->_fCacheFileExists = FALSE; } } if (hEnum) { do { if ( pcei->lpszSourceUrlName && (strnicmp(pcei->lpszSourceUrlName, s_achCookieScheme, sizeof(s_achCookieScheme) - 1 ) == 0) && (strnicmp(pcei->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 = pcei->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(&pcei->LastModifiedTime)) { pcei->LastModifiedTime.dwLowDateTime = 1; } if (CompareFileTime(pLocation->_ftCacheFileLastModified, pcei->LastModifiedTime) < 0) { pLocation->_fReadFromCacheFileNeeded = TRUE; } pLocation->_fCacheFileExists = TRUE; } dwBufferSize = sizeof(*pcei); } while (FindNextUrlCacheEntryA(hEnum, pcei, &dwBufferSize)); FindCloseUrlCache(hEnum); } // Now purge everthing we didn't get . GetCurrentGmtTime(&ftCurrent); Purge(&ftCurrent, FALSE); Cleanup: if (pcei) delete pcei; 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) || (strnicmp(pch, gcszNoScriptField, sizeof(gcszNoScriptField) - 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= and value as what ever was specified. // Example: =foo -> name: value: foo // foo -> name: value: foo // foo= -> name: foo value: 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)) { if (FParseHttpDate(pftExpire, parse.pchToken)) { // Don't make the cookie persistent if the parsing fails *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 (stricmp(parse.pchToken, gcszNoScriptField) == 0) { *pdwFlags |= COOKIE_NONSCRIPTABLE; 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; } } /* Replace non-printable characters in a string with given char This is used to enforce the RFC requirement that cookie header tokens can only contain chars in the range 0x20-0x7F. DCR: For compatability reasons we only replace control characters in the range 0x00 - 0x1F inclusive. There are international websites which depend on being able to set cookies with DBCS characters in name and value. (That assumption is wishful thinking and a violation of RFC2965.) */ void replaceControlChars(char *pszstr, char chReplace='_') { if (!pszstr) return; while (*pszstr) { if (*pszstr>=0x00 && *pszstr<=0x1F) *pszstr = chReplace; pszstr++; } } // 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; } if (pic->pszP3PPolicy) FREE_MEMORY(pic->pszP3PPolicy); 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, const char *pchPolicy ) { 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; pic->pszP3PPolicy = pchPolicy? NewString(pchPolicy) : 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 GetPromptMask(BOOL fIsSessionCookie, BOOL fIs3rdPartyCookie) { DWORD dwMask = 0x01; // prompted bit if(fIsSessionCookie) dwMask |= 0x02; if(fIs3rdPartyCookie) dwMask |= 0x04; return dwMask; } void SetCookiePromptMask( LPSTR pchRDomain, LPSTR pchPath, BOOL fSecure, BOOL f3rdParty ) { CCookieLocation *pLocation; CCookie *pCookie; FILETIME ftCurrent; GetCurrentGmtTime(&ftCurrent); CCookieCriticalSection cs; if(s_pJar->SyncWithCacheIfNeeded()) { 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)) { DWORD dwMask = GetPromptMask(!pCookie->IsPersistent(), f3rdParty); DEBUG_PRINT(HTTP, INFO, ("[MASK] SetCookiePromptMask: Domain=%s, pCookie=%#x, dwMask=%#x\n", pchRDomain, pCookie, dwMask)); pCookie->_dwPromptMask = dwMask; } } } } } } BOOL IsCookieIdentical(CCookie *pCookie, char *pchValue, DWORD dwFlags, FILETIME ftExpire) { // // Decide if we need to prompt for a cookie when one already exists. Basic idea is // if the cookie is identical, nothing is happening, so we don't need to prompt. // Change of value or expiry time (inc. session <-> persistent) means we need to // prompt again based on the new cookie type. // // no existing cookie ==> different if(NULL == pCookie) { return FALSE; } // if existing cookie has non-empty value and new value is NULL ==> different if(NULL == pchValue && pCookie->_pchValue != s_achEmpty) { return FALSE; } // different values ==> different (catches new non-empty value and existing empty value) if(pchValue && lstrcmp(pCookie->_pchValue, pchValue)) { return FALSE; } // different flags ==> different if(dwFlags != pCookie->_dwFlags) { return FALSE; } // if persistant, different expires ==> different if(memcmp(&ftExpire, &pCookie->_ftExpire, sizeof(FILETIME))) { return FALSE; } return TRUE; } DWORD CCookieJar::CheckCookiePolicy( HTTP_REQUEST_HANDLE_OBJECT *pRequest, CookieInfo *pInfo, DWORD dwOperation ) { /* Assumption: policy is checked at time of accepting cookies. Existing cookies are sent without prompt after that point */ if (dwOperation & COOKIE_OP_GET) return COOKIE_SUCCESS; DWORD dwError; DWORD dwCookiesPolicy; BOOL fCleanupPcdi = FALSE; COOKIE_DLG_INFO *pcdi = NULL, *pcdi_result = NULL; SetLastError(ERROR_SUCCESS); // // Deal first with the basic quick cases to determine if we need UI here. // they are: /// - Do we allow UI for this given request? // if (pInfo->pchURL == NULL) { return COOKIE_FAIL; } if (pRequest && (pRequest->GetOpenFlags() & INTERNET_FLAG_NO_UI)) { return 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 // if(dwOperation & COOKIE_OP_SET) { CCookieCriticalSection cs; CCookieLocation *pLocation; if (!SyncWithCacheIfNeeded()) return COOKIE_FAIL; pLocation = GetLocation(pInfo->pchRDomain, pInfo->pchPath, FALSE /* no creation*/); if (pLocation) { CCookie *pCookie; pLocation->ReadCacheFileIfNeeded(); pCookie = pLocation->GetCookie(pInfo->pchName, FALSE /* no creation */); if(IsCookieIdentical(pCookie, pInfo->pchValue, pInfo->dwFlags, pInfo->ftExpire)) { return COOKIE_SUCCESS; } } } // // Now make the async request, to see if we can put up UI // { DWORD dwAction; DWORD dwResult; DWORD dwDialogToShow; LPVOID *ppParams; pcdi = new COOKIE_DLG_INFO; if(NULL == pcdi) { return COOKIE_FAIL; } memset(pcdi, 0, sizeof(*pcdi)); pcdi->dwOperation = dwOperation; fCleanupPcdi = TRUE; if(dwOperation & COOKIE_OP_SESSION) { // make sure flags have session so it shows up right in the UI pInfo->dwFlags |= COOKIE_SESSION; } // create data to pass to dialog pcdi->pic = MakeInternetCookie(pInfo->pchURL, pInfo->pchRDomain, pInfo->pchPath, pInfo->pchName, pInfo->pchValue, pInfo->dwFlags, pInfo->ftExpire, pInfo->pP3PState ? pInfo->pP3PState->pszP3PHeader : NULL ); if(pcdi->pic == NULL) { delete pcdi; return COOKIE_FAIL; } pcdi_result = pcdi; dwError = ChangeUIBlockingState( (HINTERNET) pRequest, ERROR_HTTP_COOKIE_NEEDS_CONFIRMATION_EX, &dwAction, &dwResult, (LPVOID *)&pcdi_result ); if(dwError != ERROR_IO_PENDING && dwError != ERROR_SUCCESS) { goto quit; } switch (dwAction) { case UI_ACTION_CODE_NONE_TAKEN: { // fallback to old behavior const int MaxConcurrentDialogs = 10; static HANDLE hUIsemaphore = CreateSemaphore(NULL, MaxConcurrentDialogs, MaxConcurrentDialogs, NULL); // restrict number of concurrent dialogs // NOTE: this is a *temporary* solution for #13393 // revisit the problem of serializing dialogs when prompting behavior // for script is finalized. if (WAIT_TIMEOUT==WaitForSingleObject(hUIsemaphore, 0)) { dwError = ERROR_INTERNET_NEED_UI; break; } dwError = ConfirmCookie(NULL, pRequest, pcdi); ReleaseSemaphore(hUIsemaphore, 1, NULL); // If user requested decision to persist, save prompt result in history // "dwStopWarning" may contain 0 (no policy change) or COOKIE_ALLOW_ALL // or COOKIE_DONT_ALLOW_ALL if(pcdi->dwStopWarning) { int decision = (pcdi->dwStopWarning == COOKIE_ALLOW_ALL) ? COOKIE_STATE_ACCEPT : COOKIE_STATE_REJECT; ReverseString(pInfo->pchRDomain); cookieUIhistory.saveDecision(pInfo->pchRDomain, NULL, decision); ReverseString(pInfo->pchRDomain); } break; } case UI_ACTION_CODE_USER_ACTION_COMPLETED: // If user requested decision to persist, save prompt result in history // "dwStopWarning" may contain 0 (no policy change) or COOKIE_ALLOW_ALL // or COOKIE_DONT_ALLOW_ALL if(pcdi_result->dwStopWarning) { int decision = (pcdi_result->dwStopWarning == COOKIE_ALLOW_ALL) ? COOKIE_STATE_ACCEPT : COOKIE_STATE_REJECT; ReverseString(pInfo->pchRDomain); cookieUIhistory.saveDecision(pInfo->pchRDomain, NULL, decision); ReverseString(pInfo->pchRDomain); } if (pcdi != pcdi_result) { // got an old pcdi back, clean it if(pcdi_result->pic) { DestroyInternetCookie(pcdi_result->pic); } delete pcdi_result; } // make sure we clean current, too INET_ASSERT(fCleanupPcdi); dwError = dwResult; break; case UI_ACTION_CODE_BLOCKED_FOR_USER_INPUT: // // Go pending while we wait for the UI to be shown // INET_ASSERT(pcdi == pcdi_result); fCleanupPcdi = 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; } } quit: if ( fCleanupPcdi ) { if(pcdi->pic) { DestroyInternetCookie(pcdi->pic); } delete pcdi; } SetLastError(dwError); if (dwError != ERROR_SUCCESS) { if ( dwError == ERROR_IO_PENDING ) { return COOKIE_PENDING; } else { return COOKIE_FAIL; } } else { return COOKIE_SUCCESS; } } DWORD CCookieJar::SetCookie(HTTP_REQUEST_HANDLE_OBJECT *pRequest, const char *pchURL, char *pchHeader, DWORD &dwFlags, P3PCookieState *pState = NULL, LPDWORD pdwAction = NULL) { 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 = COOKIE_FAIL; BOOL fWriteToCacheFileNeeded; CCookieLocation *pLocation; DWORD dwOperation = COOKIE_OP_SET; DWORD dwReqAction = COOKIE_STATE_UNKNOWN; 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; } // Remove control-characters and other non-ASCII symbols replaceControlChars(pchName); replaceControlChars(pchValue); replaceControlChars(pchHeaderPath); replaceControlChars(pchHeaderRDomain); if (!pchHeaderRDomain) pchHeaderRDomain = pchDocumentRDomain; if (!pchHeaderPath) pchHeaderPath = pchDocumentPath; // We need to discard any extra info (i.e. query strings and fragments) // from the url. if (pchHeaderPath) { PTSTR psz = pchHeaderPath; while (*psz) { if (*psz==TEXT('?') || *psz==TEXT('#')) { *psz = TEXT('\0'); break; } psz++; } } // // Delete the cookie? // GetCurrentGmtTime(&ftCurrent); fDelete = CompareFileTime(ftCurrent, ftExpire) > 0; // get 3rd part flag BOOL f3rdParty = (pRequest && pRequest->Is3rdPartyCookies()) || (dwFlags & INTERNET_COOKIE_THIRD_PARTY); if (f3rdParty) { dwOperation |= COOKIE_OP_3RD_PARTY; } // check session vs. persistent if(dwFlagsFromParse & COOKIE_SESSION) { dwOperation |= COOKIE_OP_SESSION; } else { dwOperation |= COOKIE_OP_PERSISTENT; } BOOL fSessionCookie = dwFlags & COOKIE_SESSION; /* DELETE operations are not subject to P3P, except for leashed cookies */ BOOL fP3PApplies = !fDelete && pState && (!fSessionCookie || pState->fIncSession); /* Check for the "anything-goes" mode */ BOOL fAllowAll = pState && (pState->dwEvalMode==URLPOLICY_ALLOW); /* if cookie operations are disabled, fail the operation */ if (pState && pState->dwEvalMode==URLPOLICY_DISALLOW) dwReqAction = COOKIE_STATE_REJECT; else if (fP3PApplies && !fAllowAll) { /* Since downgrading a session cookie is a NOOP, report the action as ACCEPT in that case */ if (fSessionCookie && pState->dwPolicyState==COOKIE_STATE_DOWNGRADE) dwReqAction = COOKIE_STATE_ACCEPT; dwFlags |= getImpliedCookieFlags(pState); dwReqAction = pState->dwPolicyState; } else dwReqAction = COOKIE_STATE_ACCEPT; // If prompt is required, show UI if((dwFlags & INTERNET_COOKIE_PROMPT_REQUIRED) || dwReqAction==COOKIE_STATE_PROMPT) { CookieInfo ckInfo = { pchURL, pchHeaderRDomain, pchHeaderPath, pchName, pchValue, dwFlags }; ckInfo.ftExpire = ftExpire; ckInfo.pP3PState = pState; dwRet = CheckCookiePolicy(pRequest, &ckInfo, dwOperation); if (dwRet != COOKIE_SUCCESS) { dwReqAction = COOKIE_STATE_REJECT; goto Cleanup; } else dwReqAction = COOKIE_STATE_ACCEPT; } if (dwReqAction==COOKIE_STATE_REJECT) { dwRet = COOKIE_FAIL; 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) { CCookie *pCookie = pLocation->GetCookie(pchName, FALSE); // If the cookie we are attempting to delete does not exist, // return success code if (!pCookie) { dwRet = COOKIE_SUCCESS; goto Cleanup; } /* Leashed cookies cannot be deleted from 3rd party context unless P3P is disabled completely eg when "fAllowAll" is true. EXCEPTION: allow *legacy* leashed cookies to be deleted from 3rd party */ if (pCookie->IsRestricted() && f3rdParty && !(fAllowAll || pCookie->IsLegacy())) { dwReqAction = COOKIE_STATE_REJECT; goto Cleanup; } 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); pCookie->_dwPromptMask = GetPromptMask(dwOperation & COOKIE_OP_SESSION, dwOperation & COOKIE_OP_3RD_PARTY); DEBUG_PRINT(HTTP, INFO, ("[MASK] SetCookie: Domain=%s, Updating cookie mask, pCookie=%#x, new mask=%#x\n", pchHeaderRDomain, pCookie, pCookie->_dwPromptMask)); fWriteToCacheFileNeeded |= pCookie->IsPersistent(); } } if (fWriteToCacheFileNeeded) { if (!pLocation->WriteCacheFile()) goto Cleanup; } } } dwRet = COOKIE_SUCCESS; Cleanup: if (pchDocumentRDomain) FREE_MEMORY(pchDocumentRDomain); if (pchDocumentPath) FREE_MEMORY(pchDocumentPath); if (pdwAction) *pdwAction = dwReqAction; 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_fFirstTime = TRUE; s_pJar = NULL; } void PurgeCookieJarOfStaleCookies() { FILETIME ftCurrent; if (s_pJar) { CCookieCriticalSection cs; GetCurrentGmtTime(&ftCurrent); s_pJar->Purge(&ftCurrent, TRUE); } } INTERNETAPI_(BOOL) 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) { //Bug 2110: InternetGetCookieA already considered '\0' at the end of URL. MAYBE_COPY_ANSI does it again. //We don't want to change MAYBE_COPY_ANSI, so we mpCookieData.dwSize -= 1 here. Otherwise we will overflow the heap mpCookieData.dwSize -= 1; 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; } void convertLegacyCookie(CCookie *pCookie, CCookieLocation *pLocation) { const char* gasz_OptOutName[] = {"ID", "AA002", "id", "CyberGlobalAnonymous"}; const char* gasz_OptOutValue[] = {"OPT_OUT", "optout", "OPT_OUT", "optout"}; if (GlobalLeashLegacyCookies) pCookie->_dwFlags |= INTERNET_COOKIE_IS_RESTRICTED; /* special-case opt-out cookies-- these will never get leashed */ for( int i = 0; i < sizeof( gasz_OptOutName)/sizeof(gasz_OptOutName[0]); i++) { if (!strcmp(pCookie->_pchName, gasz_OptOutName[i]) && !strcmp(pCookie->_pchValue, gasz_OptOutValue[i])) { pCookie->_dwFlags &= ~INTERNET_COOKIE_IS_RESTRICTED; break; } } // Legacy cookies are special-cased for one time only // After that they are subject to P3P. pCookie->_dwFlags |= INTERNET_COOKIE_IE6; /* we need to remember which cookies are genuine IE6 vs. upgraded legacy... */ pCookie->_dwFlags |= INTERNET_COOKIE_IS_LEGACY; pLocation->WriteCacheFile(); } // // InternetGetCookieEx only returns those cookies within domain pchURL //with a name that maches pchCookieName // INTERNETAPI_(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; DWORD dwMainSwitch = (dwFlags & INTERNET_FLAG_RESTRICTED_ZONE) ? GetCookieMainSwitch(URLZONE_UNTRUSTED) : GetCookieMainSwitch(pchURL); 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) { if (IsLegacyCookie(pCookie)) convertLegacyCookie(pCookie, pLocation); BOOL fAllow; if (dwMainSwitch==URLPOLICY_ALLOW) /* replay all cookies-- even leashed ones */ fAllow = TRUE; else if (dwMainSwitch==URLPOLICY_DISALLOW) /* suppress everything */ fAllow = FALSE; else { /* default behavior: replay the cookie, provided its not leashed or we are in 1st party context */ fAllow = !pCookie->IsRestricted() || (dwFlags & INTERNET_COOKIE_THIRD_PARTY) == 0; } BOOL fNonScriptable = (pCookie->_dwFlags & COOKIE_NONSCRIPTABLE); if (fAllow && !fNonScriptable // Check for non-scriptable cookies && pCookie->CanSend(&ftCurrent, fSecure) && (pchCookieName == NULL || StrCmp( pCookie->_pchName, pchCookieName) == 0)) { if (!fFirst) cch += 2; // for ; 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; } /* UNICODE version for InternetGetCookieEx Difference from the standard InternetGetCookie* function is addition of two parameters. Supported flags: third-party, prompt-required. */ INTERNETAPI_(BOOL) InternetGetCookieExW( IN LPCWSTR lpszUrl, IN LPCWSTR lpszCookieName OPTIONAL, IN LPWSTR lpszCookieData OPTIONAL, IN OUT LPDWORD lpdwSize, IN DWORD dwFlags, IN LPVOID lpReserved) { DEBUG_ENTER_API((DBG_INET, Bool, "InternetGetCookieExW", "%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 = InternetGetCookieExA(mpUrl.psStr, mpCookieName.psStr, mpCookieData.psStr, &mpCookieData.dwSize, dwFlags, lpReserved); *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; return FALSE; } INTERNETAPI_(BOOL) 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) 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 InternalInternetSetCookie( LPCSTR pchURL, LPCSTR pchCookieName, LPCSTR pchCookieData, DWORD dwFlags, LPVOID lpReserved ) { DEBUG_ENTER_API((DBG_INET, Bool, "InternetSetCookieA", "%q, %#x, %#x", pchURL, pchCookieName, pchCookieData )); char * pch = NULL; char * pchStart = NULL; int cch; int cchT; DWORD dwErr = ERROR_SUCCESS; BOOL fResult = FALSE; P3PCookieState CS; DWORD FlagsWithParam = INTERNET_COOKIE_EVALUATE_P3P | INTERNET_COOKIE_APPLY_P3P; BOOL fPolicy = (dwFlags & INTERNET_COOKIE_EVALUATE_P3P); BOOL fDecision = (dwFlags & INTERNET_COOKIE_APPLY_P3P); if (!pchURL || !pchCookieData || (fPolicy && fDecision)) { fResult = FALSE; dwErr = ERROR_INVALID_PARAMETER; goto done; } if (!GlobalDataInitialized) { dwErr = GlobalDataInitialize(); if (dwErr!= ERROR_SUCCESS) { fResult = FALSE; goto done; } } pch = (char *) ALLOCATE_FIXED_MEMORY(CCH_COOKIE_MAX); if (pch == NULL) { fResult = FALSE; dwErr = ERROR_NOT_ENOUGH_MEMORY; goto done; } pchStart = pch; /* The reserved parameter is used for passing in P3P header or decision */ if (fPolicy) { CS.pszP3PHeader = (char*) lpReserved; EvaluateCookiePolicy(pchURL, dwFlags & INTERNET_COOKIE_THIRD_PARTY, dwFlags & INTERNET_FLAG_RESTRICTED_ZONE, &CS); } else if (fDecision) { CookieDecision *pDecision = (CookieDecision*) lpReserved; CS.fEvaluated = TRUE; CS.dwPolicyState = pDecision->dwCookieState; CS.fIncSession = ! pDecision->fAllowSession; CS.dwEvalMode = GetCookieMainSwitch(pchURL); } cch = CCH_COOKIE_MAX - 2; // one for null terminator, one for "=" 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); // All IE6 cookies are marked with this flag to distinguish // from legacy cookies inherited from past versions. if (fPolicy || fDecision) dwFlags |= INTERNET_COOKIE_IE6; DWORD dwAction = 0; if(s_pJar->SetCookie(NULL, pchURL, pchStart, dwFlags, (fPolicy||fDecision) ? &CS : NULL, &dwAction) == COOKIE_FAIL) { if( dwAction == COOKIE_STATE_REJECT) fResult = COOKIE_STATE_REJECT; else fResult = FALSE; } else { /* Return the action taken (accept, downgrade, etc.) */ fResult = dwAction; } done: if (dwErr!=ERROR_SUCCESS) { SetLastError(dwErr); DEBUG_ERROR(INET, dwErr); } if (pchStart) FREE_MEMORY(pchStart); DEBUG_LEAVE_API(fResult); return fResult; } INTERNETAPI_(BOOL) InternetSetCookieA( LPCSTR pchURL, LPCSTR pchCookieName, LPCSTR pchCookieData ) { DWORD dwResult = InternalInternetSetCookie( pchURL, pchCookieName, pchCookieData, 0, NULL); // For IE6 InternalInternetSetCookie returns the action taken. // When the API fails or cookie is rejected, that would be REJECT which is a positive value. // Convert this to FALSE to retain semantics compatible with IE5.5 return (dwResult==COOKIE_STATE_REJECT) ? FALSE : dwResult; } BOOL seekPolicyRef(const char *pszP3PHeader, char **pszPolicyRef, LPDWORD pdwLength) { static const char gszPolicyRefField[] = "policyref"; *pszPolicyRef = FindNamedValue((char*)pszP3PHeader, gszPolicyRefField, pdwLength); return (*pszPolicyRef != NULL); } DWORD extractP3PHeader(HTTP_REQUEST_HANDLE_OBJECT *pRequest, char *pszHeader, DWORD *pdwHeaderSize) { const char gszPolicyHeaderName[] = "P3P"; const int gszHeaderSize = sizeof(gszPolicyHeaderName)-1; DWORD dwIndex = 0; return pRequest->QueryResponseHeader((LPSTR) gszPolicyHeaderName, gszHeaderSize, pszHeader, pdwHeaderSize, 0, &dwIndex); } DWORD getImpliedCookieFlags(P3PCookieState *pState) { if (!pState) return 0; DWORD dwImpliedFlags = 0; // "leash" means that the cookie will only be used in 1st party context if (pState->dwPolicyState==COOKIE_STATE_LEASH) dwImpliedFlags |= INTERNET_COOKIE_IS_RESTRICTED; // "downgrade" option forces cookies to session if (pState->dwPolicyState==COOKIE_STATE_DOWNGRADE) dwImpliedFlags |= INTERNET_COOKIE_IS_SESSION; return dwImpliedFlags; } BOOL EvaluateCookiePolicy(const char *pszURL, BOOL f3rdParty, BOOL fRestricted, P3PCookieState *pState, const char *pszHostName) { char achHostName[INTERNET_MAX_HOST_NAME_LENGTH]; // If hostname is not given, it will be derived from the URL if (!pszHostName) { URL_COMPONENTS uc; memset(&uc, 0, sizeof(uc)); uc.dwStructSize = sizeof(URL_COMPONENTS); uc.lpszHostName = achHostName; uc.dwHostNameLength = sizeof(achHostName); InternetCrackUrl(pszURL, 0, 0, &uc); pszHostName = achHostName; } /* For compatibility purposes-- If registry settings are not available default behavior is: ACCEPT all cookies without restrictions */ pState->dwPolicyState = COOKIE_STATE_ACCEPT; pState->fValidPolicy = FALSE; pState->fEvaluated = FALSE; pState->fIncSession = TRUE; pState->dwEvalMode = URLPOLICY_QUERY; DWORD dwMainSwitch = fRestricted ? GetCookieMainSwitch(URLZONE_UNTRUSTED) : GetCookieMainSwitch(pszURL); if (dwMainSwitch!=URLPOLICY_QUERY) { pState->dwEvalMode = dwMainSwitch; pState->dwPolicyState = (dwMainSwitch==URLPOLICY_ALLOW) ? COOKIE_STATE_ACCEPT : COOKIE_STATE_REJECT; return TRUE; } /* Check prompt history for past decisions made by the user about this website. */ if (cookieUIhistory.lookupDecision(pszHostName, NULL, & pState->dwPolicyState)) { pState->fValidPolicy = FALSE; } else { CCookieSettings *pSettings = NULL; CCookieSettings::GetSettings(&pSettings, pszURL, f3rdParty, fRestricted); if (pSettings) { pSettings->EvaluatePolicy(pState); pSettings->Release(); } } return TRUE; } DWORD cacheFlagFromAction(DWORD dwAction) { switch (dwAction) { case COOKIE_STATE_ACCEPT: return COOKIE_ACCEPTED_CACHE_ENTRY; case COOKIE_STATE_LEASH: return COOKIE_LEASHED_CACHE_ENTRY; case COOKIE_STATE_DOWNGRADE: return COOKIE_DOWNGRADED_CACHE_ENTRY; case COOKIE_STATE_REJECT: return COOKIE_REJECTED_CACHE_ENTRY; } return 0; } DWORD HTTP_REQUEST_HANDLE_OBJECT::ExtractSetCookieHeaders(LPDWORD lpdwHeaderIndex) { DWORD error = ERROR_HTTP_COOKIE_DECLINED; P3PCookieState CS; char *pchP3PHeader = (char *) ALLOCATE_ZERO_MEMORY(CCH_COOKIE_MAX); char *pchHeader = (char *) ALLOCATE_ZERO_MEMORY(CCH_COOKIE_MAX); if (pchP3PHeader == NULL || pchHeader == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } DWORD cbPolicy = CCH_COOKIE_MAX; if (ERROR_SUCCESS == extractP3PHeader(this, pchP3PHeader, &cbPolicy)) { CS.pszP3PHeader = pchP3PHeader; InternetIndicateStatus(INTERNET_STATUS_P3P_HEADER, (LPBYTE) CS.pszP3PHeader, cbPolicy+1); } else CS.pszP3PHeader = NULL; if (!IsResponseHeaderPresent(HTTP_QUERY_SET_COOKIE)) { error = ERROR_SUCCESS; goto CheckForPolicyRef; } BOOL fRestricted = GetOpenFlags() & INTERNET_FLAG_RESTRICTED_ZONE; EvaluateCookiePolicy(GetURL(), Is3rdPartyCookies(), fRestricted, &CS, GetHostName()); /* NULL index pointer indicates that only P3P policy is evaluated, cookies are not processed */ if (!lpdwHeaderIndex) goto SendNotification; DWORD iQuery = *lpdwHeaderIndex; DWORD cbHeader = CCH_COOKIE_MAX - 1; int cPersistent = 0; /* # of persistent cookies */ int cSession = 0; /* # of session cookies */ /* Array for storing # of cookies subject to each action */ int cCount[COOKIE_STATE_MAX+1] = { 0 }; _ResponseHeaders.LockHeaders(); while ( QueryResponseHeader( HTTP_QUERY_SET_COOKIE, pchHeader, &cbHeader, 0, &iQuery) == ERROR_SUCCESS) { // All IE6 cookies are marked with this flag to distinguish // from legacy cookies inherited from past versions. DWORD dwCookieFlags = INTERNET_COOKIE_IE6; if (_fBlockedOnPrompt) dwCookieFlags |= INTERNET_COOKIE_PROMPT_REQUIRED; pchHeader[cbHeader] = 0; DWORD dwAction; DWORD dwRet = s_pJar->SetCookie(this, GetURL(), pchHeader, dwCookieFlags, &CS, &dwAction); /* The cookie flags are passed by reference to the SetCookie() function. Upon return the requested flags will have been merged with flags from parsing */ BOOL fSession = (dwCookieFlags & COOKIE_SESSION); fSession ? cSession++ : cPersistent++; INET_ASSERT(dwAction<=COOKIE_STATE_MAX); if (dwRet == COOKIE_SUCCESS) { *lpdwHeaderIndex = iQuery; error = ERROR_SUCCESS; cCount[dwAction]++; AddCacheEntryType(cacheFlagFromAction(dwAction)); } else if (dwRet == COOKIE_PENDING) { error = ERROR_IO_PENDING; INET_ASSERT(iQuery != 0); *lpdwHeaderIndex = iQuery - 1; // back up and retry this cookie _fBlockedOnPrompt = TRUE; break; } else if (dwRet == COOKIE_FAIL) { /* Only consider cookies blocked because of privacy reasons. Other reasons for rejecting the cookie (syntax errors, incorrect domain/path etc.) are not reported */ if (dwAction==COOKIE_STATE_REJECT) { cCount[dwAction]++; AddCacheEntryType(COOKIE_REJECTED_CACHE_ENTRY); } } cbHeader = CCH_COOKIE_MAX - 1; _fBlockedOnPrompt = FALSE; } _ResponseHeaders.UnlockHeaders(); SendNotification: // Postpone notifications if user has not answered the prompt yet if (error == ERROR_IO_PENDING) goto Cleanup; else { IncomingCookieState recvState = {0}; recvState.cPersistent = cPersistent; recvState.cSession = cSession; recvState.cAccepted = cCount[COOKIE_STATE_ACCEPT]; recvState.cLeashed = cCount[COOKIE_STATE_LEASH]; recvState.cDowngraded = cCount[COOKIE_STATE_DOWNGRADE]; recvState.cBlocked = cCount[COOKIE_STATE_REJECT]; // performance optimization-- same URL as the request recvState.pszLocation = NULL; // Send notification about P3P state InternetIndicateStatus(INTERNET_STATUS_COOKIE_RECEIVED, (LPBYTE) & recvState, sizeof(recvState)); } CheckForPolicyRef: /* If P3P header contains URL of the policy-ref, this information must be communicated to WININET clients */ char *pszPolicyRef = NULL; unsigned long dwLength = 0; if (CS.pszP3PHeader && seekPolicyRef(CS.pszP3PHeader, &pszPolicyRef, &dwLength)) { pszPolicyRef[dwLength] = '\0'; // create nil-terminated string containing policy-ref URL InternetIndicateStatus(INTERNET_STATUS_P3P_POLICYREF, (LPBYTE) pszPolicyRef, dwLength+1); } Cleanup: if (pchHeader) FREE_MEMORY(pchHeader); if (pchP3PHeader) FREE_MEMORY(pchP3PHeader); return error; } INTERNETAPI_(DWORD) InternetSetCookieExW( LPCWSTR lpszUrl, LPCWSTR lpszCookieName, LPCWSTR lpszCookieData, DWORD dwFlags, DWORD_PTR dwReserved ) { DEBUG_ENTER_API((DBG_INET, Bool, "InternetSetCookieExW", "%wq, %#x, %#x, %#x, %#x", lpszUrl, lpszCookieName, lpszCookieData, dwFlags, dwReserved )); DWORD dwErr = ERROR_SUCCESS; DWORD dwResult = FALSE; MEMORYPACKET mpUrl, mpCookieName, mpCookieData, mpP3PHeader; void *lpReserved = (void*) dwReserved; 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); } /* Reserved parameter is used for passing in the P3P header */ if (dwReserved && (dwFlags & INTERNET_COOKIE_EVALUATE_P3P)) { LPWSTR pwszP3PHeader = (LPWSTR) dwReserved; ALLOC_MB(pwszP3PHeader, 0, mpP3PHeader); if (!mpP3PHeader.psStr) { dwErr = ERROR_NOT_ENOUGH_MEMORY; goto cleanup; } UNICODE_TO_ANSI(pwszP3PHeader, mpP3PHeader); lpReserved = mpP3PHeader.psStr; } dwResult = InternalInternetSetCookie(mpUrl.psStr, mpCookieName.psStr, mpCookieData.psStr, dwFlags, lpReserved); cleanup: if (dwErr!=ERROR_SUCCESS) { SetLastError(dwErr); DEBUG_ERROR(INET, dwErr); } DEBUG_LEAVE_API(dwResult); return dwResult; } INTERNETAPI_(DWORD) InternetSetCookieExA( LPCSTR lpszUrl, LPCSTR lpszCookieName, LPCSTR lpszCookieData, DWORD dwFlags, DWORD_PTR dwReserved ) { DEBUG_ENTER_API((DBG_INET, Bool, "InternetSetCookieExA", "%wq, %#x, %#x, %#x, %#x", lpszUrl, lpszCookieName, lpszCookieData, dwFlags, dwReserved )); DWORD dwResult = InternalInternetSetCookie(lpszUrl, lpszCookieName, lpszCookieData, dwFlags, (void*) dwReserved); DEBUG_LEAVE_API(dwResult); return dwResult; } DWORD HTTP_REQUEST_HANDLE_OBJECT::CreateCookieHeaderIfNeeded(int *pcCookie) { char * pchRDomain = NULL; char * pchPath = NULL; DWORD cch; int cchName; int cchValue; FILETIME ftCurrent, ftExpire; BOOL fSecure; CCookieLocation *pLocation; CCookie *pCookie; DWORD dwError = 0; DWORD dwMainSwitch = GetCookieMainSwitch(GetSecurityZone()); BOOL fNoReplay = (dwMainSwitch==URLPOLICY_DISALLOW); BOOL fReplayAll = (dwMainSwitch==URLPOLICY_ALLOW); BOOL f3rdPartyRequest = Is3rdPartyCookies(); int cCookie = 0; // # of cookies added int cSuppressed = 0; // # of cookies suppressed char * pchHeader = (char *) ALLOCATE_FIXED_MEMORY(CCH_COOKIE_MAX); if (pchHeader == NULL) { dwError = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } char *pchHeaderStart = pchHeader; // 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); memset(&ftExpire, 0, sizeof(FILETIME)); 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 (IsLegacyCookie(pCookie)) convertLegacyCookie(pCookie, pLocation); if (pCookie->CanSend(&ftCurrent, fSecure)) { pchHeader = pchHeaderStart; cch = 0; cch += cchName = strlen(pCookie->_pchName); cch += cchValue = strlen(pCookie->_pchValue); if (cchName) cch += 1; // for equal sign if (cch < CCH_COOKIE_MAX) { if (cchName > 0) { memcpy(pchHeader, pCookie->_pchName, cchName); pchHeader += cchName; *pchHeader++ = '='; } if (cchValue > 0) { memcpy(pchHeader, pCookie->_pchValue, cchValue); pchHeader += cchValue; } /* IF the cookie is marked 1st party only, OR cookie feature is not enabled for this zone, suppress the cookie */ if (fNoReplay || (!fReplayAll && f3rdPartyRequest && pCookie->IsRestricted())) { cSuppressed++; continue; } cCookie += 1; AddRequestHeader(HTTP_QUERY_COOKIE, pchHeaderStart, cch, 0, HTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON); } } // if CanSend } // for pCookie } // if IsMatch } // for UnlockHeaders(); } Cleanup: // Send notification about sent/suppressed in this request if (cCookie || cSuppressed) { OutgoingCookieState sendState = { cCookie, cSuppressed }; InternetIndicateStatus(INTERNET_STATUS_COOKIE_SENT, (LPBYTE) &sendState, sizeof(sendState)); } if (pchHeaderStart) FREE_MEMORY(pchHeaderStart); if (pchRDomain) FREE_MEMORY(pchRDomain); if (pchPath) FREE_MEMORY(pchPath); if(pcCookie) { *pcCookie = cCookie; } return dwError; } // IsDomainLegalCookieDomain - exported in wininet.w for private use.. // // example: ( "yahoo.com", "www.yahoo.com") -> TRUE // ( "com", "www.yahoo.com") -> FALSE // ( "0.255.192", "255.0.255.192") -> FALSE // ( "255.0.255.192", "255.0.255.192") -> TRUE BOOLAPI IsDomainLegalCookieDomainA( IN LPCSTR pchDomain, IN LPCSTR pchFullDomain) { BOOL returnValue = FALSE; DWORD dwError = ERROR_SUCCESS; LPSTR pchReversedDomain = NULL; LPSTR pchReversedFullDomain = NULL; long iDomainSize, iFullDomainSize; if(!pchDomain || IsBadStringPtr( pchDomain, INTERNET_MAX_URL_LENGTH)) { dwError = ERROR_INVALID_PARAMETER; goto doneIsDomainLegalCookieDomainA; } if(!pchFullDomain || IsBadStringPtr( pchFullDomain, INTERNET_MAX_URL_LENGTH)) { dwError = ERROR_INVALID_PARAMETER; goto doneIsDomainLegalCookieDomainA; } iDomainSize = strlen( pchDomain) + 1; iFullDomainSize = strlen( pchFullDomain) + 1; pchReversedDomain = new char[ iDomainSize]; pchReversedFullDomain = new char[ iFullDomainSize]; if( pchReversedDomain == NULL || pchReversedFullDomain == NULL) goto doneIsDomainLegalCookieDomainA; memcpy( pchReversedDomain, pchDomain, iDomainSize); memcpy( pchReversedFullDomain, pchFullDomain, iFullDomainSize); ReverseString( pchReversedDomain); ReverseString( pchReversedFullDomain); returnValue = IsDomainLegal( pchReversedDomain, pchReversedFullDomain); doneIsDomainLegalCookieDomainA: if( dwError != ERROR_SUCCESS) SetLastError( dwError); if( pchReversedDomain != NULL) delete [] pchReversedDomain; if( pchReversedFullDomain != NULL) delete [] pchReversedFullDomain; return returnValue; } BOOLAPI IsDomainLegalCookieDomainW( IN LPCWSTR pwchDomain, IN LPCWSTR pwchFullDomain) { MEMORYPACKET mpDomain; ALLOC_MB(pwchDomain,0,mpDomain); if (!mpDomain.psStr) { return FALSE; } UNICODE_TO_ANSI(pwchDomain, mpDomain); MEMORYPACKET mpFullDomain; ALLOC_MB(pwchFullDomain,0,mpFullDomain); if (!mpFullDomain.psStr) { return FALSE; } UNICODE_TO_ANSI(pwchFullDomain, mpFullDomain); return IsDomainLegalCookieDomainA( mpDomain.psStr, mpFullDomain.psStr); }