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.
3948 lines
110 KiB
3948 lines
110 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 "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=<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))
|
|
{
|
|
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 ; <space>
|
|
cch += cchName = strlen(pCookie->_pchName);
|
|
cch += cchValue = strlen(pCookie->_pchValue);
|
|
if (cchName && cchValue) cch += 1; // for equal sign
|
|
|
|
if (pchCookieData && cch < *pcchCookieData)
|
|
{
|
|
if (!fFirst)
|
|
{
|
|
*pchCookieData++ = ';';
|
|
*pchCookieData++ = ' ';
|
|
}
|
|
|
|
if (cchName > 0)
|
|
{
|
|
memcpy(pchCookieData, pCookie->_pchName, cchName);
|
|
pchCookieData += cchName;
|
|
|
|
if (cchValue > 0)
|
|
{
|
|
*pchCookieData++ = '=';
|
|
}
|
|
}
|
|
|
|
if (cchValue > 0)
|
|
{
|
|
memcpy(pchCookieData, pCookie->_pchValue, cchValue);
|
|
pchCookieData += cchValue;
|
|
}
|
|
}
|
|
|
|
fFirst = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//TerminateBuffer:
|
|
|
|
cch += 1;
|
|
|
|
if (pchCookieData)
|
|
{
|
|
if (cch > *pcchCookieData)
|
|
{
|
|
dwErr = ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
else
|
|
{
|
|
*pchCookieData = 0;
|
|
fSuccess = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fSuccess = TRUE;
|
|
}
|
|
|
|
if (cch == 1)
|
|
{
|
|
dwErr = ERROR_NO_MORE_ITEMS;
|
|
fSuccess = FALSE;
|
|
cch = 0;
|
|
}
|
|
|
|
*pcchCookieData = cch;
|
|
|
|
Cleanup:
|
|
|
|
if (pchRDomain)
|
|
FREE_MEMORY(pchRDomain);
|
|
if (pchPath)
|
|
FREE_MEMORY(pchPath);
|
|
|
|
done:
|
|
if (dwErr!=ERROR_SUCCESS)
|
|
{
|
|
SetLastError(dwErr);
|
|
DEBUG_ERROR(INET, dwErr);
|
|
}
|
|
DEBUG_LEAVE_API(fSuccess);
|
|
return fSuccess;
|
|
}
|
|
|
|
/*
|
|
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);
|
|
}
|