You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1475 lines
41 KiB
1475 lines
41 KiB
//---------------------------------------------------------------------------
|
|
//
|
|
// COOKIE.CXX
|
|
//
|
|
// Cookie Jar
|
|
//
|
|
// This file implements cookies as defined by Navigator 4 behavior and the
|
|
// specification at http://www.netscape.com/newsref/std/cookie_spec.html.
|
|
// If Navigator 4 and the specification are not in agreement, we try to
|
|
// match the Navigator 4 behavior.
|
|
//
|
|
// The following describes some interesting aspects of cookie behavior.
|
|
//
|
|
// SYNTAX
|
|
//
|
|
// Syntax for cookie is
|
|
//
|
|
// [[name]=] value [; options]
|
|
//
|
|
// The name is everything before "=" with leading and trailing whitespace
|
|
// removed. The value is everything after "=" and before ";" with leading
|
|
// and trailing whitespace removed. The name and value can contain spaces,
|
|
// quotes or any other character except ";" and "=". The name and equal
|
|
// sign are optional.
|
|
//
|
|
// Example: =foo -> name: <blank> value: foo
|
|
// foo -> name: <blank> value: foo
|
|
// foo= -> name: foo value: <blank>
|
|
// ; -> name: <blank> value: <blank>
|
|
//
|
|
// ORDER
|
|
//
|
|
// Cookies with a more specific path are sent before cookies with
|
|
// a less specific path mapping. The domain does not contibute
|
|
// to the ordering of cookies.
|
|
//
|
|
// If the path length of two cookies are equal, then the cookies
|
|
// are ordered by time of creation. Navigator maintains this
|
|
// ordering across domain and path boundaries. IE maintains this
|
|
// ordering for a specific domain and path. It is difficult to match
|
|
// the Navigator behavior and there are no known bugs because of
|
|
// this difference.
|
|
//
|
|
// MATCHING
|
|
//
|
|
// Path matches are done at the character level. Any
|
|
// directory structure in the path is ignored.
|
|
//
|
|
// Navigator matches domains at the character level and ignores
|
|
// the structure of the domain name.
|
|
//
|
|
// Previous versions of IE tossed the leading "." on a domain name.
|
|
// With out this information, character by character compares are
|
|
// can produce incorrect results. For backwards compatibilty with
|
|
// old cookie we continue to match on a component by component basis.
|
|
//
|
|
// Some examples of the difference are:
|
|
//
|
|
// Cookie domain Document domain Navigator match IE match
|
|
// .foo.com foo.com no yes
|
|
// bar.x.com foobar.x.com yes no
|
|
//
|
|
// ACCEPTING COOKIES
|
|
//
|
|
// A cookie is rejected if the path specified in the set cookie
|
|
// header is not a prefix of document's path.
|
|
//
|
|
// Navigator rejects a cookie if the domain specified in the
|
|
// set cookie header does not contain at least two periods
|
|
// or the domain is not a suffix of the document's domain.
|
|
// The suffix match is done on a character by character basis.
|
|
//
|
|
// Navigator ignores all the stuff in the specification about
|
|
// three period matching and the seven special top level domains.
|
|
//
|
|
// IE rejects a cookie if the domain specified by the cookie
|
|
// header does not contain at least one embedded period or the
|
|
// domain is not a suffix of the documents domain.
|
|
//
|
|
// Cookies are accepted if the path specified in the set cookie
|
|
// header is a prefix of the document's path and the domain
|
|
// specified in the set cookie header.
|
|
//
|
|
// The difference in behavior is a result of the matching rules
|
|
// described in the previous section.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
#include <wininetp.h>
|
|
#include "httpp.h"
|
|
//#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=<blank> and value as what ever was specified.
|
|
// Example: =foo -> name: <blank> value: foo
|
|
// foo -> name: <blank> value: foo
|
|
// foo= -> name: foo value: <blank>
|
|
|
|
if (ParseToken(&parse, FALSE, TRUE))
|
|
{
|
|
*ppchName = parse.pchToken;
|
|
if (parse.fEqualFound)
|
|
{
|
|
if (ParseToken(&parse, FALSE, FALSE))
|
|
{
|
|
*ppchValue = parse.pchToken;
|
|
}
|
|
else
|
|
{
|
|
*ppchValue = s_achEmpty;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*ppchValue = *ppchName;
|
|
*ppchName = s_achEmpty;
|
|
}
|
|
}
|
|
|
|
while (ParseToken(&parse, FALSE, TRUE))
|
|
{
|
|
if (stricmp(parse.pchToken, "expires") == 0)
|
|
{
|
|
if (parse.fEqualFound && ParseToken(&parse, TRUE, FALSE, 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);
|
|
}
|