//--------------------------------------------------------------------------- // // 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 "..\inc\cookie.h" #include "cookiejar.h" #define CCH_COOKIE_MAX (5 * 1024) static char s_achEmpty[] = ""; // 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 struct CookieInfo { char *pchRDomain; char *pchPath; char *pchName; char *pchValue; unsigned long dwFlags; FILETIME ftExpiration; }; //--------------------------------------------------------------------------- // // String utilities // //--------------------------------------------------------------------------- static char * StrnDup(const char *pch, int cch) { char *pchAlloc = (char *)ALLOCATE_MEMORY(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; } // if the match is incomplete, its not a valid path match. if (*pchPrefix != 0) return FALSE; // // Furthermore, reject matches like '/path1/' and '/path1/../path2' // We could try to canonicalize pchStr, but its sufficient to just //say any path with a '..' after the matched prefix doesn't match the //prefix. Usually WinHTTP canonicalizes the path before this point //anyhow. // while (*pchStr != 0) { pchStr++; if (*(pchStr-1) == '.' && *(pchStr) == '.') { return FALSE; } } return TRUE; } 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) { UNREFERENCED_PARAMETER(pchHeader); UNREFERENCED_PARAMETER(pchDocument); return TRUE; } 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. // VENKATK: moved in following changes from wininet: // if last substring <= 2, and not .tv, and // second-last substring <=2 OR one of restricted special domains, // then it's not a minimal domain. int cEmbeddedDotsNeeded = 1; if(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'; } } 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; } } BOOL PathAndRDomainFromURL(const char *pchURL, char **ppchRDomain, char **ppchPath, BOOL *pfSecure, BOOL bStrip) { 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; dwError = CrackUrl((char *)pchURL, 0, FALSE, &ustScheme, NULL, // Scheme Name NULL, // Scheme Lenth &pchDomainBuf, &cchDomain, TRUE, 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_UNKNOWN) { // // known scheme should be supported // e.g. 3rd party pluggable protocol should be able to // call cookie api to setup cookies... // SetLastError(ERROR_INVALID_PARAMETER); goto Cleanup; } *pfSecure = ustScheme == INTERNET_SCHEME_HTTPS; 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(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; } return fSuccess; } //--------------------------------------------------------------------------- // // CCookieBase implementation // //--------------------------------------------------------------------------- void * CCookieBase::operator new(size_t cb, size_t cbExtra) { void *pv = ALLOCATE_MEMORY(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(cchValue); if (!_pchValue) { _pchValue = s_achEmpty; SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } memcpy(_pchValue, pchValue, cchValue); } return TRUE; } BOOL CCookie::CanSend(BOOL fSecure) { return (fSecure || !(_dwFlags & COOKIE_SECURE)); } BOOL CCookie::PurgeAll(void *) { return TRUE; } BOOL WriteString(HANDLE hFile, const char *pch) { DWORD cb; return pch && *pch ? WriteFile(hFile, pch, strlen(pch), &cb, NULL) : TRUE; } BOOL WriteStringLF(HANDLE hFile, const char *pch) { DWORD cb; if (!WriteString(hFile, pch)) return FALSE; return WriteFile(hFile, "\n", 1, &cb, NULL); } //--------------------------------------------------------------------------- // // 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; } void CCookieLocation::Purge(BOOL (CCookie::*pfnPurge)(void *), void *pv) { CCookie **ppCookie = &_pCookieKids; CCookie *pCookie; while ((pCookie = *ppCookie) != NULL) { if ((pCookie->*pfnPurge)(pv)) { *ppCookie = pCookie->_pCookieNext; delete pCookie; } else { ppCookie = &pCookie->_pCookieNext; } } } BOOL CCookieLocation::IsMatch(const char *pchRDomain, const char *pchPath) { return IsDomainMatch(_pchRDomain, pchRDomain) && IsPathMatch(_pchPath, pchPath); } //--------------------------------------------------------------------------- // // CCookieJar implementation // //--------------------------------------------------------------------------- CCookieJar * CCookieJar::Construct() { return new(0) CCookieJar(); } CCookieJar::CCookieJar() { _csCookieJar.Init(); } 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; 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) { 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::expireCookies(CCookieLocation *pLocation, FILETIME *pftNow) { FILETIME ftCurrent; if (pftNow==NULL) GetSystemTimeAsFileTime(&ftCurrent); else ftCurrent = *pftNow; CCookie **previous = & pLocation->_pCookieKids; CCookie *pCookie = pLocation->_pCookieKids; while (pCookie) { /* Session cookies do not expire so we only check persistent cookies */ if ((pCookie->_dwFlags & COOKIE_SESSION) == 0) { /* "CompareFileTime" macro returns {+1, 0, -1} similar to "strcmp" */ int cmpresult = CompareFileTime(ftCurrent, pCookie->_ftExpiry); if (cmpresult==1) /* Cookie has expired: remove from linked list & delete */ { *previous = pCookie->_pCookieNext; delete pCookie; pCookie = *previous; continue; } } /* Otherwise cookie is still valid: advance to next node */ previous = & (pCookie->_pCookieNext); pCookie = pCookie->_pCookieNext; } } CCookieLocation* CCookieJar::GetCookies(const char *pchRDomain, const char *pchPath, CCookieLocation *pLast, FILETIME *ftCurrentTime) { for (CCookieLocation *pLocation = pLast ? pLast->_pLocationNext : *GetBucket(pchRDomain); pLocation; pLocation = pLocation->_pLocationNext) { if (pLocation->IsMatch(pchRDomain, pchPath)) { /* Found matching cookies... Before returning linked list to the user, check expiration times, deleting cookies which are no longer valid */ expireCookies(pLocation, ftCurrentTime); return pLocation; } } /* Reaching this point means no matching cookies were found */ return NULL; } void CCookieJar::Purge() { // NOT IMPLEMENTED } // // PARSE tracks both the last parsed token and the current buffer location for ParseToken() // pchToken points to the parsed token. // pchBuffer points to the next location in the buffer to be parsed. // fEqualFound indicates if the token was terminated by an equal sign. // 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, BOOL fAllowQuotedToken = FALSE) { char ch; char *pch; char *pchEndToken; pParse->fEqualFound = FALSE; pch = SkipWS(pParse->pchBuffer); if (*pch == 0) { pParse->pchToken = pch; return FALSE; } if (*pch == '\"' && fAllowQuotedToken) { pch++; // Return the token as begining after the quote pParse->pchToken = pch; // Scan until the next quote. while(*pch != '\0' && *pch != '\"') { pch++; } // Expecting final quote. If not found, error. //If found, return the quoted token. if (*pch == '\0') { pParse->pchBuffer = pch; return FALSE; } else { INET_ASSERT(*pch == '\"'); *pch = '\0'; pch++; pParse->pchBuffer = pch; return TRUE; } } 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, "Max-Age", sizeof("Max-Age") - 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, CookieInfo *pCookie ) { char **ppchName = & (pCookie->pchName); char **ppchValue = & (pCookie->pchValue); char **ppchPath = & (pCookie->pchPath); char **ppchRDomain = & (pCookie->pchRDomain); PARSE parse; parse.pchBuffer = pchHeader; *ppchName = NULL; *ppchValue = NULL; *ppchPath = NULL; *ppchRDomain = NULL; pCookie->dwFlags = 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, TRUE)) { // WinHttpX treats persistent cookies as session cookies with expiration if (FParseHttpDate(& pCookie->ftExpiration, parse.pchToken)) { // Don't make the cookie persistent if the parsing fails // If the parsing succeeds and we have an expiration time, //make sure its not session so the expiry is used. pCookie->dwFlags &= ~COOKIE_SESSION; } } } else if (stricmp(parse.pchToken, "Max-Age") == 0) { // excerpt below from http://www.ietf.org/rfc/rfc2109.txt // //Max-Age=delta-seconds // Optional. The Max-Age attribute defines the lifetime of the // cookie, in seconds. The delta-seconds value is a decimal non- // negative integer. After delta-seconds seconds elapse, the client // should discard the cookie. A value of zero means the cookie // should be discarded immediately. // if (parse.fEqualFound && ParseToken(&parse, TRUE, FALSE, TRUE)) { LPSTR szValue = parse.pchToken; DWORD dwMaxAge; FILETIME ftExpires; // Try to extract the Max-Age value. If its malformed, set //MaxAge to 0 to flush the cookie. if (!ExtractDword( &szValue, strlen(parse.pchToken), &dwMaxAge)) { dwMaxAge = 0; } // Add the max age to the current file time to compute the //expiration time. GetSystemTimeAsFileTime(&ftExpires); AddLongLongToFT(&ftExpires, ((LONGLONG)dwMaxAge) * (LONGLONG)10000000); pCookie->ftExpiration = ftExpires; // Make sure the cookie is no longer considered a session //cookie so that the expiration time is actually applied. pCookie->dwFlags &= ~COOKIE_SESSION; } } else if (stricmp(parse.pchToken, "domain") == 0) { if (parse.fEqualFound ) { if( ParseToken(&parse, TRUE, FALSE, TRUE)) { // 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, TRUE)) { *ppchPath = parse.pchToken; } else { *ppchPath = s_achEmpty; } } else if (stricmp(parse.pchToken, "secure") == 0) { pCookie->dwFlags |= COOKIE_SECURE; if (parse.fEqualFound) { ParseToken(&parse, TRUE, FALSE, TRUE); } } else { if (parse.fEqualFound) { ParseToken(&parse, TRUE, FALSE, TRUE); } } } if (!*ppchName) { *ppchName = *ppchValue = s_achEmpty; } } DWORD CCookieJar::SetCookie(HTTP_REQUEST_HANDLE_OBJECT *pRequest, const char *pchURL, char *pchHeader, DWORD dwFlags = 0) { char *pchDocumentRDomain = NULL; char *pchDocumentPath = NULL; BOOL fDocumentSecure; DWORD dwRet = SET_COOKIE_FAIL; CCookieLocation *pLocation; BOOL fHaveCookieJarLock = FALSE; CookieInfo cookieStats; UNREFERENCED_PARAMETER(pRequest); ParseHeader(pchHeader, &cookieStats); char *pchName = cookieStats.pchName; char *pchValue = cookieStats.pchValue; char *pchHeaderPath = cookieStats.pchPath; char *pchHeaderRDomain = cookieStats.pchRDomain; DWORD dwFlagsFromParse = cookieStats.dwFlags; // merge flags given with those found by the parser. dwFlags |= dwFlagsFromParse; if (!PathAndRDomainFromURL(pchURL, &pchDocumentRDomain, &pchDocumentPath, &fDocumentSecure, FALSE)) 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; // 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++; } } // WinHttpX treats persistent cookies as session cookies, subject // to the expiration rules // Also it does not implement zone policies set by URLMON // // Finally, we can add the cookie! // { if (_csCookieJar.Lock()) { fHaveCookieJarLock = TRUE; pLocation = GetLocation(pchHeaderRDomain, pchHeaderPath, TRUE); if (pLocation) { CCookie *pCookie; pCookie = pLocation->GetCookie(pchName, TRUE); if (!pCookie) goto Cleanup; // // If the cookie's value or flags have changed, update it. // if (pCookie->SetValue(pchValue)) { pCookie->_dwFlags = dwFlags; pCookie->_ftExpiry = cookieStats.ftExpiration; } else { // Oops, alloc failed. This cookie is toast, lets expire it. pCookie->_dwFlags = dwFlags; pCookie->_dwFlags &= ~COOKIE_SESSION; // session cookies don't expire.. pCookie->_ftExpiry.dwLowDateTime = 0; pCookie->_ftExpiry.dwHighDateTime = 0; } } _csCookieJar.Unlock(); fHaveCookieJarLock = FALSE; } } dwRet = SET_COOKIE_SUCCESS; Cleanup: if (fHaveCookieJarLock) _csCookieJar.Unlock(); if (pchDocumentRDomain) FREE_MEMORY(pchDocumentRDomain); if (pchDocumentPath) FREE_MEMORY(pchDocumentPath); return dwRet; } //--------------------------------------------------------------------------- // // External APIs // //--------------------------------------------------------------------------- CCookieJar * CreateCookieJar() { return CCookieJar::Construct(); } void CloseCookieJar(CCookieJar * CookieJar) { if (CookieJar) { delete CookieJar; } } #ifndef WININET_SERVER_CORE void PurgeCookieJar() { } #endif // // 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 *pchHeader = NULL; DWORD cbHeader; DWORD iQuery = 0; int cCookies = 0; DWORD error = ERROR_WINHTTP_HEADER_NOT_FOUND; const DWORD cbHeaderInit = CCH_COOKIE_MAX * sizeof(char) - 1; pchHeader = New char[CCH_COOKIE_MAX]; if (pchHeader == NULL || !_ResponseHeaders.LockHeaders()) { error = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } INET_ASSERT(lpdwHeaderIndex); cbHeader = cbHeaderInit; iQuery = *lpdwHeaderIndex; while (QueryResponseHeader(HTTP_QUERY_SET_COOKIE, pchHeader, &cbHeader, 0, &iQuery) == ERROR_SUCCESS) { pchHeader[cbHeader] = 0; INTERNET_HANDLE_OBJECT *pRoot = GetRootHandle (this); CCookieJar* pcj = pRoot->_CookieJar; DWORD dwRet = pcj->SetCookie(this, GetURL(), pchHeader); 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 = cbHeaderInit; } _ResponseHeaders.UnlockHeaders(); Cleanup: if (pchHeader) delete [] pchHeader; return error; } VOID HTTP_REQUEST_HANDLE_OBJECT::CreateCookieHeaderIfNeeded(VOID) { char * pchRDomain = NULL; char * pchPath = NULL; BOOL fSecure, fHeadersLocked=FALSE; DWORD cch = 0; int cchName; int cchValue; char * pchHeader = NULL; char * pchHeaderStart = NULL; DWORD dwIndex = 0; if (!LockHeaders()) goto Cleanup; fHeadersLocked = TRUE; // // Before the first call to SendRequest, the WinHttp client may set custom //cookies by setting the HTTP_QUERY_COOKIE header. This header, or an empty //string, will be saved off to _CachedCookieHeader so it can be reused on subsequent //calls to SendRequest on the same request handle. // // If on subsequent calls the WinHttp client removes the cookies header, //we will honor that change by clearing the _CachedCookieHeader to an empty string. //All other changes to the cookie header are ignored. // // (this forces the requirement that the WinHttp client sets custom cookies //before the first SendRequest, but does allow the client to clear the custom //cookies later if need be, perhaps in response to a redirect) // switch (QueryRequestHeader(HTTP_QUERY_COOKIE, NULL, &cch, 0, &dwIndex)) { default: case ERROR_WINHTTP_HEADER_NOT_FOUND: // // If there is currently no Cookie request header (perhaps it got // discarded by the application during a callback notification), // then ensure that the app's cached Cookie request header is also // marked as empty. // if (_CachedCookieHeader && _CachedCookieHeader[0] != '\0') { FREE_MEMORY(_CachedCookieHeader); _CachedCookieHeader = NULL; } if (_CachedCookieHeader == NULL) { _CachedCookieHeader = (LPSTR) ALLOCATE_FIXED_MEMORY(1); if (_CachedCookieHeader == NULL) goto Cleanup; _CachedCookieHeader[0] = '\0'; } break; case ERROR_INSUFFICIENT_BUFFER: // If there is a cached Cookie request header, then set it as the // initial value of the Cookie header. Additional cookies from // the Cookie Jar will be appended to this. if (_CachedCookieHeader) { if (_CachedCookieHeader[0] == '\0') { ReplaceRequestHeader(HTTP_QUERY_COOKIE, NULL, 0, 0, 0); } else { ReplaceRequestHeader(HTTP_QUERY_COOKIE, _CachedCookieHeader, lstrlen(_CachedCookieHeader), 0, 0); } } // // If the application sets a Cookie request header before calling // SendRequest, then cache its value. After the first SendRequest // call, we know _CachedCookieHeader will be non-NULL indicating that // the current cookie request header may have been generated by // WinHttp and therefore should not be cached. // else { _CachedCookieHeader = (LPSTR) ALLOCATE_FIXED_MEMORY(cch + 1); if (_CachedCookieHeader == NULL) goto Cleanup; if (!QueryRequestHeader(HTTP_QUERY_COOKIE, _CachedCookieHeader, &cch, 0, &dwIndex)) { FREE_MEMORY(_CachedCookieHeader); _CachedCookieHeader = NULL; goto Cleanup; } } break; } if (!PathAndRDomainFromURL(GetURL(), &pchRDomain, &pchPath, &fSecure, FALSE)) goto Cleanup; fSecure = GetOpenFlags() & WINHTTP_FLAG_SECURE; { INTERNET_HANDLE_OBJECT *pRoot = GetRootHandle (this); CCookieJar* pcj = pRoot->_CookieJar; if (pcj->_csCookieJar.Lock()) { FILETIME ftNow; GetSystemTimeAsFileTime(&ftNow); CCookieLocation *pLocation = pcj->GetCookies(pchRDomain, pchPath, NULL, &ftNow); while (pLocation) { for (CCookie *pCookie = pLocation->_pCookieKids; pCookie; pCookie = pCookie->_pCookieNext) { if (pCookie->CanSend(fSecure)) { if (pchHeaderStart == NULL) { pchHeaderStart = (char *) ALLOCATE_FIXED_MEMORY(CCH_COOKIE_MAX * sizeof(char)); if (pchHeaderStart == NULL) break; } 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; } AddRequestHeader(HTTP_QUERY_COOKIE, pchHeaderStart, cch, 0, HTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON); } } // if (CanSend) } // for (pCookie) pLocation = pcj->GetCookies(pchRDomain, pchPath, pLocation, &ftNow); } // while (pLocation) pcj->_csCookieJar.Unlock(); } // if pcj->_csCookieJar.Lock() } Cleanup: if (fHeadersLocked) UnlockHeaders(); if (pchHeaderStart) FREE_MEMORY(pchHeaderStart); if (pchRDomain) FREE_MEMORY(pchRDomain); if (pchPath) FREE_MEMORY(pchPath); }