mirror of https://github.com/lianthony/NT4.0
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.
1279 lines
32 KiB
1279 lines
32 KiB
//
|
|
// cookie.c - contains common HTTP cookies code
|
|
//
|
|
// created by Arthur Bierer (t-artb) 9/2/95
|
|
//
|
|
// Notes: CookieJar is my internal hash table that I keep open
|
|
// to store Cookies as I find them.
|
|
//***PERF:**** we may consider resuing the path and domain
|
|
// since their the same and could save space.
|
|
// PERF: Lets us keep the Path And Name in the same string
|
|
// which will be stored in the HASH string
|
|
// PERF: Change file format so we store things grouped by
|
|
// their path/name hash, rather than seperate cookies
|
|
// this will save us from having to hash each cookie.
|
|
|
|
|
|
|
|
#include "all.h"
|
|
#include "history.h"
|
|
#include "oharever.h"
|
|
|
|
|
|
#ifdef COOKIES
|
|
|
|
#define MAX_COOKIES 300 // as mentioned in Netscape spec
|
|
#define MAX_COOKIES_DYN 500
|
|
#define HIGH 90
|
|
#define LOW 60
|
|
#define MAX_COOKIE_SIZE 4*1024
|
|
|
|
|
|
#define LOW_MARK ((MAX_COOKIES*LOW)/100)
|
|
#define HIGH_MARK ((MAX_COOKIES_DYN*HIGH)/100)
|
|
|
|
|
|
#define COOKIE_IS_SECURE 0x01 // set if the cookie can only be sent over secure conns
|
|
#define COOKIE_IS_DELETED 0x02 // marked for deletion
|
|
|
|
|
|
|
|
struct CookieType {
|
|
char *szName;
|
|
char *szValue;
|
|
DCACHETIME *pExpires; // FALSE if expires on end of session
|
|
DWORD dwFlags; // for now SECURE is the only flag
|
|
DCACHETIME dcLast; // last time this cookie was accessed.
|
|
struct CookieType *pNext; // linked list of cookies.
|
|
};
|
|
|
|
int NumOfCookiesInCookieJar;
|
|
|
|
struct hash_table *pTheCookieJar = NULL; // hash table that stores our cookies.
|
|
typedef DCACHETIME COOKIETIME;
|
|
|
|
const char cszCookie[] = "Cookie";
|
|
const char cszSecure[] = "secure";
|
|
const char cszComma[] = ",";
|
|
const char cszExpires[] = "expires";
|
|
const char cszCommaSpace[] = ", ";
|
|
const char cszDomain[] = "domain";
|
|
const char cszPath[] = "path";
|
|
const char cszScanFStr[] = "%s\n";
|
|
const char cszScanFCookie[] = "%u\n%u\n%u\n%u\n%u\n*\n";
|
|
const char cszPrintFCookie[] = "%s\n%s\n%s\n%u\n%u\n%u\n%u\n%u\n*\n";
|
|
const char cszCookieFileName[] = "cookies.txt";
|
|
const char cszWrite[] = "w";
|
|
const char cszTerm[] = "*\n";
|
|
const char cszRead[] = "r";
|
|
const char cszDate[] = "Date";
|
|
const char cszSetCookie[] = "Set-Cookie";
|
|
const char cszEmpString[] = "\0";
|
|
//const char cszCIFLineFmtV[]="V,%s,%lu\n";
|
|
//const char cszProductName[]=VER_PRODUCTNAME_STR;
|
|
extern const char cszCIFLineFmtV[];
|
|
extern const char cszProductName[];
|
|
|
|
|
|
static VOID PurgeCookieJarOfStaleCookies();
|
|
static char *SlamSlashOnToCookiePath(char *);
|
|
|
|
//
|
|
// FreeCookie - literally eats a cookie by freeing up its memory
|
|
//
|
|
// pCookie - pointer to cookie to be free-ed
|
|
//
|
|
static VOID FreeCookie( struct CookieType *pCookie)
|
|
{
|
|
if ( pCookie )
|
|
{
|
|
if ( pCookie->szName )
|
|
GTR_FREE( pCookie->szName );
|
|
if ( pCookie->szValue )
|
|
GTR_FREE(pCookie->szValue);
|
|
if ( pCookie->pExpires)
|
|
GTR_FREE(pCookie->pExpires);
|
|
|
|
GTR_FREE(pCookie);
|
|
}
|
|
}
|
|
|
|
|
|
// ScanCookieMapping - walks through a list of cookie name=value mapping looking
|
|
// for a match, returns the mapping if found.
|
|
//
|
|
// pCookie - start of linked list of cookies
|
|
// szName - name to search for
|
|
// returns: NULL if not found, otherwise a pointer to a matched cookie
|
|
//
|
|
static struct CookieType *ScanCookieMapping(struct CookieType *pCookie, const char *szName)
|
|
{
|
|
while ( pCookie )
|
|
{
|
|
if ( _stricmp(szName, pCookie->szName ) == 0 )
|
|
return pCookie; // found
|
|
|
|
// go to next cookie
|
|
pCookie = pCookie->pNext;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// RemoveFromCookieMapping - walks a linked list and removes pMapping if found
|
|
//
|
|
// pCookie - pointer to cookie that should be removed from linkedlist
|
|
// ppRemoveFromMapping - pointer to pointer of the first cookie->next pointer
|
|
// (returns) - TRUE on success or FALSE on failure
|
|
//
|
|
// NOTE: Assumes Caller will deallocate deleted pCookie
|
|
static BOOL RemoveFromCookieMapping(struct CookieType *pCookie,
|
|
struct CookieType **ppRemoveFromMapping)
|
|
{
|
|
ASSERT(ppRemoveFromMapping);
|
|
ASSERT(pCookie);
|
|
|
|
while ( *ppRemoveFromMapping )
|
|
{
|
|
if ( (*ppRemoveFromMapping) == pCookie )
|
|
{
|
|
*ppRemoveFromMapping = pCookie->pNext;
|
|
FreeCookie(pCookie);
|
|
return TRUE; // found
|
|
}
|
|
// get the address of where the next pointer is stored
|
|
|
|
ppRemoveFromMapping = &(*ppRemoveFromMapping)->pNext;
|
|
}
|
|
|
|
ASSERT(0); // we always expect to be able to delete something
|
|
|
|
return FALSE; // failure
|
|
}
|
|
|
|
//
|
|
// CreateCookieJar - Builds our internal cookie hash table
|
|
//
|
|
// returns: TRUE on sucess, FALSE on failure
|
|
//
|
|
static BOOL CreateCookieJar()
|
|
{
|
|
if ( pTheCookieJar )
|
|
return TRUE; // already created
|
|
|
|
pTheCookieJar = Hash_Create();
|
|
|
|
if ( pTheCookieJar == NULL )
|
|
return FALSE;
|
|
|
|
NumOfCookiesInCookieJar = 0;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
// IsCookieStale - Determines whether a give cookie is still valid, or if it is expired.
|
|
//
|
|
// pCurTime - current date/time
|
|
// pCookie - cookie to check
|
|
// bNoExpireStale - If TRUE no expiration cookies ( ie pCookie->pExpires == NULL )
|
|
// are Stale ( usually set TRUE on shutdown of IE ) otherwise that are not
|
|
//
|
|
// returns: TRUE if Stale and Invalid, FALSE if valid
|
|
//
|
|
static BOOL IsCookieStale(DCACHETIME *pCurTime, struct CookieType *pCookie, BOOL bNoExpireStale)
|
|
{
|
|
ASSERT(pCurTime);
|
|
ASSERT(pCookie);
|
|
|
|
if ( pCookie->pExpires == NULL )
|
|
return bNoExpireStale; // its stale at the end of the session
|
|
|
|
return (CompareDCacheTime(*pCookie->pExpires, *pCurTime) <= 0);
|
|
}
|
|
|
|
|
|
|
|
// CookieToHash - Takes a Domain and Path string, allocates, and then
|
|
// concats them into a string that it returns
|
|
//
|
|
// szDomain - pointer to domain string that will contain host or domain
|
|
// szPath - pointer to Path string
|
|
// (returns) pointer to allocated concated string
|
|
//
|
|
static VOID CookieToHash( char *szDest, char *szDomain, char *szPath, int iMaxStringSize )
|
|
{
|
|
int iDomainLen = 0;
|
|
|
|
ASSERT(szPath);
|
|
ASSERT(szDomain);
|
|
ASSERT(szPath[0]=='/');
|
|
|
|
|
|
iDomainLen = strlen(szDomain);
|
|
iDomainLen = min(iDomainLen+1, iMaxStringSize );
|
|
|
|
// copy the domain, making sure it doesn't over flow the buffer
|
|
strncpy(szDest,szDomain,iDomainLen);
|
|
|
|
// chk to make sure the path doesn't overflow the buffer
|
|
if ( (iDomainLen + strlen(szPath) ) > iMaxStringSize )
|
|
{
|
|
ASSERT(0); // fail
|
|
return;
|
|
}
|
|
// concat the path on.
|
|
strcat(szDest,szPath);
|
|
}
|
|
|
|
// CookieToHeader - converts a cookie structure to a header line that can be sent
|
|
// Allocates a new header if *ppHeaderLine == NULL.
|
|
//
|
|
// pCookie - cookie to convert
|
|
// ppHeaderLine - pointer to pointer to where the header should be allocated, or appended
|
|
// returns: TRUE on success, FALSE on failure
|
|
//
|
|
static BOOL CookieToHeader( struct CookieType *pCookie, HTHeaderList **ppHeaderLine )
|
|
{
|
|
HTHeaderSVList *pSubHeader;
|
|
char szPrevDelm[] = ";"; // cannot be const, since compiler gives error, plus this is copied
|
|
// in header _SetNameValue function
|
|
char *pPrevDelm = &szPrevDelm[0]; // this is a default case
|
|
|
|
ASSERT(pCookie);
|
|
ASSERT(ppHeaderLine);
|
|
|
|
// if we don't have a Header allocated yet, then do so now.
|
|
if ( *ppHeaderLine == NULL )
|
|
{
|
|
*ppHeaderLine = HTHeaderList_New();
|
|
if ( *ppHeaderLine == NULL )
|
|
return FALSE; // fail
|
|
|
|
HTHeaderList_SetNameValue(*ppHeaderLine,
|
|
cszCookie,
|
|
NULL);
|
|
pPrevDelm = NULL;
|
|
}
|
|
|
|
// create a sub-value list
|
|
pSubHeader = HTHeaderSVList_New();
|
|
|
|
if ( pSubHeader == NULL )
|
|
{
|
|
GTR_FREE(*ppHeaderLine);
|
|
return FALSE;
|
|
}
|
|
|
|
// stick the name=value pair in the list, with the appropriate delim.
|
|
HTHeaderSVList_SetNameValue( pSubHeader,
|
|
pCookie->szName,
|
|
pCookie->szValue,
|
|
pPrevDelm);
|
|
|
|
// put it in the header list.
|
|
HTHeaderSVList_Append(*ppHeaderLine, pSubHeader);
|
|
|
|
return TRUE; // success
|
|
}
|
|
|
|
|
|
|
|
|
|
// HeaderToCookie - converts a Header Line into a Cookie structure
|
|
// pHeaderLine: Input Header to read in
|
|
// ppszDomain: pointer to pointer of a possible domain string,
|
|
// ppszHost: pointer to pointer of a possible path string,
|
|
// (returns): an allocted, converted pointer to a cookie
|
|
//
|
|
// Note: includes two hacks to recombine string that contain
|
|
// commas into one string. see comments below
|
|
static struct CookieType *HeaderToCookie( HTHeaderList *pHeaderLine,
|
|
char **ppszDomain, char **ppszPath )
|
|
{
|
|
HTHeaderSVList * sub_value;
|
|
struct CookieType *pNewCookie;
|
|
|
|
ASSERT(pHeaderLine);
|
|
ASSERT(ppszDomain);
|
|
ASSERT(ppszPath);
|
|
|
|
// initalize Host and Path to default case
|
|
*ppszDomain = NULL;
|
|
*ppszPath = NULL;
|
|
|
|
// get the first value in the sublist
|
|
sub_value = pHeaderLine->sub_value;
|
|
|
|
// allocate a new cookie
|
|
pNewCookie = GTR_CALLOC( 1, sizeof(*pNewCookie) );
|
|
|
|
if ( pNewCookie == NULL )
|
|
return NULL;
|
|
|
|
// go through each and every sub-value in the Set-Cookie header looking for
|
|
// information to convert to cookie data.
|
|
while ( sub_value )
|
|
{
|
|
// a NULL value, we might ignore this guy
|
|
if ( sub_value->value == NULL || sub_value->name == NULL )
|
|
{
|
|
if ( sub_value->name )
|
|
{
|
|
if ( _stricmp( sub_value->name, cszSecure ) == 0 )
|
|
{
|
|
pNewCookie->dwFlags |= COOKIE_IS_SECURE;
|
|
}
|
|
}
|
|
|
|
// go on to the next item
|
|
sub_value = sub_value->next;
|
|
continue;
|
|
}
|
|
|
|
// If we got here, then we have name and value pair...
|
|
|
|
if ( _stricmp( sub_value->name, cszExpires ) == 0)
|
|
{
|
|
// They have an expires= entry
|
|
DCACHETIME *pExpires = GTR_MALLOC(sizeof(*pExpires));
|
|
|
|
if ( pExpires == NULL )
|
|
goto LErr_HeaderToCookie;
|
|
|
|
if ( FParseDate(pExpires,sub_value->value) )
|
|
{
|
|
pNewCookie->pExpires = pExpires;
|
|
}
|
|
else
|
|
{
|
|
// we failed to parse the date, but we recover gracefully
|
|
GTR_FREE(pExpires);
|
|
// leave pNewCookie->pExpires field NULL, and continue on..
|
|
}
|
|
}
|
|
else if ( _stricmp( sub_value->name, cszDomain ) == 0)
|
|
{
|
|
// we have a Domain = line
|
|
// lets save its pointer
|
|
|
|
ASSERT(sub_value->value);
|
|
|
|
// if its .foo.ucsd.edu, make it foo.ucsd.edu since
|
|
// the . can only make our hash func less effective
|
|
if ( sub_value->value[0] == '.' )
|
|
*ppszDomain = sub_value->value+1;
|
|
else
|
|
*ppszDomain = sub_value->value;
|
|
|
|
}
|
|
else if ( _stricmp( sub_value->name, cszPath ) == 0)
|
|
{
|
|
// we have a Path = line
|
|
// lets save its pointer
|
|
ASSERT(sub_value->value);
|
|
|
|
*ppszPath = sub_value->value;
|
|
}
|
|
else
|
|
{
|
|
// If we got here it must be a NAME=VALUE entry!
|
|
//
|
|
// if someone has more than one more than one cookie on the
|
|
// same line we take the last one.. boy is this is bad
|
|
if ( pNewCookie->szName )
|
|
{
|
|
GTR_FREE(pNewCookie->szName);
|
|
if ( pNewCookie->szValue )
|
|
GTR_FREE(pNewCookie->szValue);
|
|
}
|
|
|
|
pNewCookie->szName = GTR_strdup(sub_value->name);
|
|
pNewCookie->szValue = GTR_strdup(sub_value->value);
|
|
|
|
if ( pNewCookie->szValue == NULL || pNewCookie->szName == NULL )
|
|
goto LErr_HeaderToCookie;
|
|
}
|
|
|
|
// go on to the next item
|
|
sub_value = sub_value->next;
|
|
}
|
|
|
|
// if we went through the whole conversion and it has no
|
|
// name, then thats bad, lets throw it away
|
|
|
|
if ( pNewCookie->szName == NULL )
|
|
{
|
|
|
|
LErr_HeaderToCookie:
|
|
|
|
FreeCookie(pNewCookie);
|
|
return NULL;
|
|
}
|
|
|
|
return pNewCookie;
|
|
}
|
|
|
|
// StreamToCookie - reads input from a file pointer, and converts it
|
|
// into an newly allocated cookie structure.
|
|
//
|
|
// pFileStorage - input stream to read cookie from
|
|
// ppCookie - pointer to a pointer where the cookie will be stored
|
|
// ( NOTE: only allocates cookie if *ppCookie == NULL )
|
|
//
|
|
// returns: FALSE on failure , TRUE on success
|
|
|
|
#define ARR_ELE_NAME 0
|
|
#define ARR_ELE_VALUE 1
|
|
#define ARR_ELE_HASH 2
|
|
#define ARR_ELE_SIZE 3
|
|
|
|
#define NUM_OF_SCANF_ENTRIES 5
|
|
|
|
static BOOL StreamToCookie(FILE *pFileStorage, struct CookieType **ppCookie,
|
|
char *pszInputBuffer, int iMaxBufSize, char **ppszHash)
|
|
{
|
|
char *aStringBuffers[ARR_ELE_SIZE];
|
|
char *pszEndInputBuffer ;
|
|
int RetVal;
|
|
int i;
|
|
|
|
ASSERT(ppCookie);
|
|
ASSERT(pFileStorage);
|
|
ASSERT(pszInputBuffer);
|
|
ASSERT(ppszHash);
|
|
|
|
|
|
// if its NULL then we need to allocate the cookie
|
|
if ( *ppCookie == NULL )
|
|
{
|
|
*ppCookie = GTR_CALLOC( 1, sizeof(**ppCookie) );
|
|
if ( *ppCookie == NULL )
|
|
return FALSE;
|
|
}
|
|
|
|
(*ppCookie)->pExpires = GTR_MALLOC(sizeof(DCACHETIME));
|
|
if ( (*ppCookie)->pExpires == NULL )
|
|
{
|
|
goto LErrStreamToCookie;
|
|
}
|
|
|
|
pszEndInputBuffer = pszInputBuffer + iMaxBufSize; // make backup copy
|
|
i = 0;
|
|
|
|
do {
|
|
RetVal = fscanf(pFileStorage, cszScanFStr,
|
|
pszInputBuffer );
|
|
|
|
if ( RetVal < 1 || RetVal == EOF )
|
|
goto LErrStreamToCookie;
|
|
|
|
aStringBuffers[i] = pszInputBuffer;
|
|
|
|
// make it the pointer at the end of the string readin
|
|
pszInputBuffer += strlen(aStringBuffers[i])+1;
|
|
|
|
// make sure we don't have a string that is bigger than 4K.
|
|
if ( pszInputBuffer > pszEndInputBuffer)
|
|
{
|
|
ASSERT(0); // we don't expect this case !! BUGBUG this will corrupt
|
|
goto LErrStreamToCookie;
|
|
}
|
|
i++;
|
|
|
|
} while ( i < ARR_ELE_SIZE );
|
|
|
|
|
|
RetVal = fscanf(pFileStorage, cszScanFCookie,
|
|
&(*ppCookie)->dwFlags,
|
|
&(*ppCookie)->pExpires->dwDCacheTime1,
|
|
&(*ppCookie)->pExpires->dwDCacheTime2,
|
|
&(*ppCookie)->dcLast.dwDCacheTime1,
|
|
&(*ppCookie)->dcLast.dwDCacheTime2 );
|
|
|
|
if ( RetVal < NUM_OF_SCANF_ENTRIES || RetVal == EOF )
|
|
goto LErrStreamToCookie;
|
|
|
|
(*ppCookie)->szName = GTR_strdup(aStringBuffers[ARR_ELE_NAME]);
|
|
(*ppCookie)->szValue = GTR_strdup(aStringBuffers[ARR_ELE_VALUE]);
|
|
*ppszHash = aStringBuffers[ARR_ELE_HASH];
|
|
|
|
|
|
if ( (*ppCookie)->szName == NULL ||
|
|
(*ppCookie)->szValue == NULL ||
|
|
*ppszHash == NULL )
|
|
{
|
|
LErrStreamToCookie:
|
|
FreeCookie(*ppCookie);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// CookieToStream - writes a cookie structure to a file stream
|
|
//
|
|
// pCookie - pointer to a fully valid cookie structure containing cookie info
|
|
// pFileStorage - pointer to stream to flush
|
|
//
|
|
static VOID CookieToStream(struct CookieType *pCookie, char *pszHash, FILE *pFileStorage)
|
|
{
|
|
ASSERT(pFileStorage);
|
|
ASSERT(pCookie);
|
|
ASSERT(pCookie->szValue); // if it has not value string? what do we do?
|
|
ASSERT(pCookie->szName);
|
|
ASSERT(pszHash);
|
|
ASSERT(pCookie->pExpires);
|
|
|
|
|
|
fprintf(pFileStorage, cszPrintFCookie,
|
|
pCookie->szName,
|
|
pCookie->szValue,
|
|
pszHash,
|
|
pCookie->dwFlags,
|
|
pCookie->pExpires->dwDCacheTime1,
|
|
pCookie->pExpires->dwDCacheTime2,
|
|
pCookie->dcLast.dwDCacheTime1,
|
|
pCookie->dcLast.dwDCacheTime2 );
|
|
}
|
|
|
|
// WriteCookieJar - loops through the entries in the cookiejar, then deletes,
|
|
// and frees the cookiejar
|
|
//
|
|
// pFileStorage - contains a file pointer to save cookies to, if NULL the cookies
|
|
// are not saved.
|
|
//
|
|
VOID WriteCookieJar( )
|
|
{
|
|
int i, cookies;
|
|
struct CookieType *pCookie, *pNukeCookie;
|
|
DCACHETIME dcCurTime;
|
|
CHAR szFullPath[MAX_PATH];
|
|
FILE *fpStorage = NULL;
|
|
char *pszHash;
|
|
|
|
// get cache directory
|
|
PREF_GetRootDirectory(szFullPath);
|
|
ASSERT((strlen(szFullPath)+ARRAY_ELEMENTS(cszCookieFileName)) < ARRAY_ELEMENTS(szFullPath) );
|
|
strcat(szFullPath, cszCookieFileName );
|
|
|
|
// if we have cookies lets write them out
|
|
if ( NumOfCookiesInCookieJar > 0 )
|
|
fpStorage = fopen(szFullPath, cszWrite );
|
|
|
|
// put begining terminator * in.
|
|
if ( fpStorage )
|
|
{
|
|
fprintf(fpStorage, cszCIFLineFmtV, cszProductName, VER_PRODUCTVERSION_DW);
|
|
SetDCacheTime(&dcCurTime);
|
|
}
|
|
|
|
// go through and flush them to disk, as well as freeing memory
|
|
|
|
for (i=0, cookies=Hash_Count(pTheCookieJar); i<cookies; i++)
|
|
{
|
|
// get the cookie list
|
|
Hash_GetIndexedEntry(pTheCookieJar, i, &pszHash, NULL, (void **)&pCookie);
|
|
|
|
// walk the list of cookies, deleteing each one as we go
|
|
while ( pCookie)
|
|
{
|
|
pNukeCookie = pCookie;
|
|
pCookie = pCookie->pNext;
|
|
|
|
// if the cookie is not expired, and valid after this session
|
|
// and there is a file stream, then write it out
|
|
|
|
if ( fpStorage && !IsCookieStale(&dcCurTime, pNukeCookie, TRUE) )
|
|
CookieToStream(pNukeCookie, pszHash, fpStorage);
|
|
|
|
FreeCookie(pNukeCookie);
|
|
}
|
|
|
|
}
|
|
|
|
Hash_Destroy(pTheCookieJar);
|
|
pTheCookieJar = NULL;
|
|
|
|
if ( fpStorage )
|
|
fclose(fpStorage);
|
|
}
|
|
|
|
|
|
// UpdateCookieJar - Adds, removes, or replaces a cookie
|
|
//
|
|
// pCookieToDie - Cookie to add, remove or delete
|
|
// ( WILL be invalid pointer after call ie freed)
|
|
// bDelMode - TRUE if we're removing this cookie, FALSE if adding or replacing
|
|
//
|
|
// (returns): TRUE if we overwrote, deleted, or added another mapping to it,
|
|
// FALSE if we failed to the desired operation
|
|
// Notes: Adds if an exact cookie match cannot be found, otherwise replaces the
|
|
// matched cookie
|
|
static BOOL UpdateCookieJar(struct CookieType *pCookieToDie, char *pszHash, BOOL bDelMode)
|
|
{
|
|
struct CookieType *pCookie;
|
|
struct CookieType *pMapping = NULL;
|
|
int ndx;
|
|
BOOL bIsFirstOne = FALSE;
|
|
BOOL fReturnVal = FALSE;
|
|
|
|
ASSERT(pCookieToDie);
|
|
ASSERT(pszHash);
|
|
|
|
ndx = Hash_Find(pTheCookieJar, pszHash, NULL, (void **)&pCookie);
|
|
if (ndx == -1)
|
|
{
|
|
// not around aka WE DID NOT FOUND IT
|
|
|
|
if ( bDelMode )
|
|
{
|
|
FreeCookie(pCookieToDie);
|
|
return FALSE; // no delete, nothing around to delete
|
|
}
|
|
|
|
// DO ADD HERE !!
|
|
|
|
Hash_Add(pTheCookieJar, pszHash, NULL, (void *) pCookieToDie);
|
|
|
|
goto LRetDoAdd;
|
|
}
|
|
|
|
// we found it lets scan it, first the first element,
|
|
// and then the linked list that it contains
|
|
|
|
ASSERT(pCookie);
|
|
|
|
pMapping = ScanCookieMapping(pCookie, pCookieToDie->szName);
|
|
|
|
// did we find anything ?
|
|
|
|
if ( pMapping )
|
|
{
|
|
if ( bDelMode )
|
|
{
|
|
if ( pCookie->pNext == NULL ) // one entry in the linked list
|
|
{
|
|
// ah-ha we have a match lets kill this sucker.
|
|
// this is the first element, and the only element, since
|
|
// its next pointer is NULL
|
|
Hash_DeleteIndexedEntry(pTheCookieJar, ndx);
|
|
FreeCookie(pCookie);
|
|
}
|
|
else
|
|
{
|
|
// otherwise, lets remove it from the mapping list aka the linked list
|
|
RemoveFromCookieMapping(pMapping, &pCookie );
|
|
|
|
// if the first element has changed, put the second item
|
|
// in the hash table
|
|
Hash_SetData(pTheCookieJar, ndx, (void *) pCookie);
|
|
}
|
|
FreeCookie(pCookieToDie); // the death cookies dies here
|
|
|
|
// DO DELETION HERE !
|
|
NumOfCookiesInCookieJar--;
|
|
return TRUE; // did a delete
|
|
}
|
|
|
|
// otherwise we just replace it.
|
|
// DO REPLACE HERE !
|
|
|
|
// first remove old copy if its in the list
|
|
RemoveFromCookieMapping(pMapping, &pCookie );
|
|
|
|
pCookieToDie->pNext = pCookie;
|
|
|
|
// then swap in our CookieType structure since we're newer
|
|
Hash_SetData(pTheCookieJar, ndx, (void *) pCookieToDie);
|
|
|
|
return TRUE; // we did a succesful replace
|
|
}
|
|
|
|
|
|
// NO, WE DID NOT FIND ANYTHING under that specific path and host-domain
|
|
|
|
if ( bDelMode )
|
|
{
|
|
FreeCookie(pCookieToDie);
|
|
return FALSE; // we didn;t find anything to delete
|
|
}
|
|
|
|
//
|
|
// otherwise we place the old item in the mapping list,
|
|
// and let the new item replace him in path hash
|
|
|
|
// DO ADD HERE !!
|
|
|
|
// then swap in our CookieType structure since we're newer
|
|
Hash_SetData(pTheCookieJar, ndx, (void *) pCookieToDie);
|
|
pCookieToDie->pNext = pCookie; // add old cookie onto our list end
|
|
|
|
LRetDoAdd:
|
|
|
|
NumOfCookiesInCookieJar++;
|
|
|
|
|
|
PurgeCookieJarOfStaleCookies();
|
|
|
|
return TRUE; // did an add
|
|
}
|
|
|
|
// OpenTheCookieJar - opens the cookies jar by performing initaliztion, and
|
|
// reading input from a cookies.txt file to fill persistant cookies
|
|
//
|
|
// returns: TRUE on success or file open failure, FALSE otherwise
|
|
//
|
|
#define MAX_FILE_HEADER 80
|
|
BOOL OpenTheCookieJar()
|
|
{
|
|
CHAR szFullPath[MAX_PATH];
|
|
CHAR szPeekStr[MAX_FILE_HEADER];
|
|
struct CookieType *pCookie = NULL;
|
|
FILE *fpStorage = NULL;
|
|
char *pszInputBuffer = NULL;
|
|
char *pszHash = NULL;
|
|
BOOL bRet = TRUE;
|
|
|
|
if ( ! CreateCookieJar() )
|
|
return FALSE;
|
|
|
|
PREF_GetRootDirectory(szFullPath);
|
|
strcat(szFullPath, cszCookieFileName );
|
|
|
|
fpStorage = fopen(szFullPath, cszRead );
|
|
|
|
if ( fpStorage == NULL )
|
|
return TRUE; // don't fail, if the file isn't around, we will create it later
|
|
|
|
if ( fgets(szPeekStr, MAX_FILE_HEADER, fpStorage ) == NULL )
|
|
goto LOpenReturn; // don't fail, if we had a bad cookie file, just don't read it
|
|
|
|
|
|
if ( szPeekStr[0] != 'V' && szPeekStr[1] != ',' )
|
|
goto LOpenReturn; // see comments above., ie don't fail on bad file format
|
|
|
|
pszInputBuffer = GTR_MALLOC(MAX_COOKIE_SIZE);
|
|
|
|
if ( pszInputBuffer == NULL )
|
|
{
|
|
bRet = FALSE;
|
|
goto LOpenReturn;
|
|
}
|
|
|
|
while ( StreamToCookie( fpStorage, &pCookie, pszInputBuffer, MAX_COOKIE_SIZE, &pszHash ) )
|
|
{
|
|
UpdateCookieJar(pCookie, pszHash, FALSE) ;
|
|
pCookie = NULL; // set it back to NULL so we force a new cookie to be created.
|
|
}
|
|
|
|
LOpenReturn:
|
|
|
|
fclose(fpStorage);
|
|
if ( pszInputBuffer )
|
|
GTR_FREE(pszInputBuffer);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
// AddCookieToHeader - Takes a cookie, and determines whether it is valid to add to an
|
|
// outgoing HTTP header.
|
|
//
|
|
// pFirstCookie - the first cookie in a linked list of cookies for a path&host
|
|
// pTheCurDate - cur date/time to chk for invalid cookies ( expired)
|
|
// pHeader - header we will add valid cookies to
|
|
// bIsSecure - TRUE if we're on a secure connection, FALSE otherwise
|
|
//
|
|
// (returns) TRUE if we deleted all cookies in this hash., FALSE otherwise
|
|
static BOOL AddCookieToHeader(struct CookieType *pFirstCookie, DCACHETIME *pTheCurDate,
|
|
HTHeaderList **ppHeader, BOOL bIsSecure )
|
|
{
|
|
struct CookieType *pCookie;
|
|
|
|
ASSERT(pFirstCookie);
|
|
ASSERT(pTheCurDate);
|
|
|
|
// we got a particular path and domain combo
|
|
// now lets walk its mappings and see if there are any to delete.
|
|
|
|
pCookie = pFirstCookie;
|
|
while ( pCookie )
|
|
{
|
|
if ( IsCookieStale( pTheCurDate, pCookie, ( ppHeader ? FALSE : TRUE )) )
|
|
{
|
|
pCookie = pCookie->pNext; // go on, since we skip stale cookies
|
|
continue;
|
|
}
|
|
if ( ppHeader )
|
|
{
|
|
if ( bIsSecure ||
|
|
(!bIsSecure && !(pCookie->dwFlags & COOKIE_IS_SECURE)) )
|
|
{
|
|
// otherwise lets add it to the HEADER !!!
|
|
CookieToHeader( pCookie, ppHeader );
|
|
}
|
|
}
|
|
// go on..
|
|
pCookie = pCookie->pNext;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// PurgeDeletedCookiesInCookieList - removes the cookies from the cookie list,
|
|
// by walking the list looking for stale cookies to remove.
|
|
//
|
|
// ndx - index into hash where the list is
|
|
// pFirstCookie - the first cookie in the list
|
|
//
|
|
static BOOL PurgeDeletedCookiesInCookieList(int ndx,
|
|
struct CookieType *pFirstCookie )
|
|
{
|
|
struct CookieType *pCookie;
|
|
BOOL bReturn = FALSE;
|
|
|
|
ASSERT(pFirstCookie);
|
|
|
|
// we got a particular path and domain combo
|
|
// now lets walk its mappings and see if there are any to delete.
|
|
|
|
pCookie = pFirstCookie;
|
|
while ( pCookie )
|
|
{
|
|
if ( pCookie->dwFlags & COOKIE_IS_DELETED)
|
|
{
|
|
struct CookieType *pTempCookie;
|
|
|
|
// DO DELETION HERE !!!
|
|
NumOfCookiesInCookieJar--;
|
|
|
|
// make sure not to trash it before
|
|
// we have the next cookie
|
|
pTempCookie = pCookie->pNext; // go on
|
|
|
|
RemoveFromCookieMapping(pCookie, &pFirstCookie);
|
|
|
|
pCookie = pTempCookie;
|
|
continue;
|
|
}
|
|
|
|
// go on..
|
|
pCookie = pCookie->pNext;
|
|
}
|
|
|
|
// after going through the mappings, we could have deleted ALL mappings
|
|
// for a particlar path, so lets look to see if that is true.
|
|
|
|
if ( pFirstCookie == NULL )
|
|
{
|
|
Hash_DeleteIndexedEntry(pTheCookieJar, ndx);
|
|
bReturn = TRUE; // we deleted all cookies in hash
|
|
}
|
|
else
|
|
{
|
|
// if its not we could have deleted one in the linked list,
|
|
// and that could have changed who is the first
|
|
// cookie, so it couldn't hurt to reset it
|
|
|
|
Hash_SetData(pTheCookieJar, ndx, (void *) pFirstCookie);
|
|
}
|
|
|
|
return bReturn; // tells whether we deleted all cookies
|
|
}
|
|
|
|
// CompareCookieLastAccessedTimes - compares two Cookie pointers to see if
|
|
// they are equal, less, or greater than in "last accessed time"
|
|
// Is called by qsort.
|
|
//
|
|
// pvCookie1 - void pointer to element in array where cookie pointer is stored
|
|
// pvCookie2 - void pointer to element in array where cookie pointer is stored
|
|
//
|
|
static int _cdecl CompareCookieLastAccessedTimes(const void *pvCookie1, const void *pvCookie2)
|
|
{
|
|
struct CookieType *pCookie1;
|
|
struct CookieType *pCookie2;
|
|
|
|
ASSERT(pvCookie1);
|
|
ASSERT(pvCookie2);
|
|
|
|
pCookie1 = *((struct CookieType **)pvCookie1);
|
|
pCookie2 = *((struct CookieType **)pvCookie2);
|
|
|
|
ASSERT(pCookie1);
|
|
ASSERT(pCookie2);
|
|
|
|
return (CompareDCacheTime(pCookie1->dcLast, pCookie2->dcLast));
|
|
}
|
|
|
|
|
|
|
|
// PurgeCookieJarOfStaleCookies - walks through the cache looking for expired headers,
|
|
// then removes them if they are expired
|
|
//
|
|
// pTheCurDate - pointer to the date time to use when chking for expiration
|
|
// ( can be NULL if not known )
|
|
//
|
|
static VOID PurgeCookieJarOfStaleCookies()
|
|
{
|
|
int i, j;
|
|
struct CookieType *pCookie;
|
|
struct CookieType *rgpCookies[MAX_COOKIES_DYN];
|
|
|
|
// not ready to do purging?
|
|
if ( NumOfCookiesInCookieJar < HIGH_MARK)
|
|
return;
|
|
|
|
// build an array of cookie pointers
|
|
for (i=(Hash_Count(pTheCookieJar)-1), j=0; i>=0; i--)
|
|
{
|
|
Hash_GetIndexedEntry(pTheCookieJar, i, NULL, NULL, (void **)&pCookie);
|
|
|
|
while ( pCookie )
|
|
{
|
|
rgpCookies[j++] = pCookie;
|
|
pCookie = pCookie->pNext;
|
|
}
|
|
// keep going on ..
|
|
}
|
|
|
|
ASSERT( j == NumOfCookiesInCookieJar );
|
|
|
|
// we sort the cookies
|
|
qsort( (void *) rgpCookies, j, sizeof(struct CookieType *),
|
|
CompareCookieLastAccessedTimes );
|
|
|
|
// we mark the oldest ones as deleted, we keep going until we hit the low water mark
|
|
// we start with j being the NumOfCookiesInCookieJar and keep going until our count is
|
|
// below LOW_MARK
|
|
for ( i = 0; j >= LOW_MARK; i++, j--)
|
|
{
|
|
rgpCookies[i]->dwFlags |= COOKIE_IS_DELETED;
|
|
}
|
|
|
|
// now we clean up the ones that were marked deleted
|
|
for (i=Hash_Count(pTheCookieJar)-1; i>=0; i--)
|
|
{
|
|
// grab the list for this hash item
|
|
Hash_GetIndexedEntry(pTheCookieJar, i, NULL, NULL, (void **)&pCookie);
|
|
|
|
// now purge any deleted cookies in this list
|
|
PurgeDeletedCookiesInCookieList(i, pCookie);
|
|
}
|
|
|
|
ASSERT( j == NumOfCookiesInCookieJar );
|
|
}
|
|
|
|
#ifdef USE_COOKIEPARSEBACKWARDS
|
|
|
|
// CookieParsePathBackwards - parse a cookie backwards, pulling "/"s out
|
|
static char *CookieParsePathBackwards(char *szBeg, char *szEnd )
|
|
{
|
|
ASSERT(szBeg);
|
|
ASSERT(szEnd);
|
|
|
|
while ( szBeg != szEnd )
|
|
{
|
|
if ( *szEnd == '/' )
|
|
{
|
|
*szEnd = '\0';
|
|
break;
|
|
}
|
|
|
|
szEnd--;
|
|
}
|
|
|
|
// if we've hit the end we terminate the /
|
|
if ( szBeg == szEnd )
|
|
szBeg[1] = '\0';
|
|
|
|
|
|
return szEnd;
|
|
}
|
|
|
|
#endif
|
|
|
|
// SlamSlashOnToCookiePath - takes a path, and figures out whether it
|
|
// needs to a add '/' onto the begining of the path string
|
|
//
|
|
// szNonSlashPath - The path to add a slash to
|
|
// (returns): pointer to newly allocated / string or the orginal string
|
|
//
|
|
static char *SlamSlashOnToCookiePath(char *szNonSlashPath)
|
|
{
|
|
// handle special case where a path doesn't have a beg / in it
|
|
// if that is so, lets put one there
|
|
|
|
if ( szNonSlashPath && szNonSlashPath[0] != '/' )
|
|
{
|
|
char *szTemp;
|
|
|
|
szTemp = GTR_MALLOC(strlen(szNonSlashPath)+2);
|
|
if ( szTemp == NULL )
|
|
{
|
|
GTR_FREE(szNonSlashPath);
|
|
return NULL;
|
|
}
|
|
|
|
szTemp[0] = '/';
|
|
szTemp[1] = '\0';
|
|
strcat(szTemp, szNonSlashPath);
|
|
GTR_FREE(szNonSlashPath);
|
|
return szTemp;
|
|
}
|
|
|
|
return szNonSlashPath;
|
|
}
|
|
|
|
// x_CreateCookieHeaderIfNeeded - Scans a given URL ( host and path ) to determine
|
|
// if there are cookies that need to be sent to this URL. If there are, this func
|
|
// will place them in header. Also checks to see if the cookies requires a "secure"
|
|
// connection.
|
|
//
|
|
// header - out going HTTP header to add lines to
|
|
// szUrl - URL we're sending too
|
|
// bIsSecure - TRUE if our connection is secure ( ex: https: )
|
|
//
|
|
BOOL x_CreateCookieHeaderIfNeeded( HTHeader *header, const char *szUrl, BOOL bIsSecure)
|
|
{
|
|
DCACHETIME dcTime;
|
|
char *szHost;
|
|
char *szPathBeg;
|
|
char *szPHost; // parse pointer
|
|
char *szPathEnd;
|
|
char *szLastHost ; // parse pointer
|
|
char *szLastPath = NULL;
|
|
char szHash[MAX_URL_STRING];
|
|
struct CookieType *pCookie;
|
|
int ndx;
|
|
HTHeaderList *pHeaderList = NULL; // list of subvalues to add to the header
|
|
|
|
ASSERT(szUrl);
|
|
ASSERT(header);
|
|
|
|
SetDCacheTime(&dcTime);
|
|
|
|
szHost = HTParse(szUrl, cszEmpString, PARSE_HOST);
|
|
|
|
if ( szHost == NULL )
|
|
return FALSE;
|
|
|
|
szPathBeg = HTParse(szUrl, cszEmpString, PARSE_PATH);
|
|
|
|
// add a slash if needed to our path,
|
|
// this important since its the common way for us to store our path
|
|
szPathBeg = SlamSlashOnToCookiePath(szPathBeg);
|
|
|
|
if ( szPathBeg == NULL )
|
|
{
|
|
szPathBeg = GTR_MALLOC(5);
|
|
if ( szPathBeg == NULL )
|
|
return FALSE;
|
|
|
|
szPathBeg[0] = '/';
|
|
szPathBeg[1] = '\0';
|
|
}
|
|
|
|
szPathEnd = strlen(szPathBeg)+szPathBeg;
|
|
|
|
// now scan for the path in the host hash table
|
|
while ( szPathBeg && szPathEnd != szLastPath )
|
|
{
|
|
// reset the host string to the begining
|
|
szPHost = szHost;
|
|
szLastHost = NULL;
|
|
|
|
while( szPHost && szPHost != szLastHost)
|
|
{
|
|
// remove the period from being the first characeter
|
|
if ( szPHost[0] == '.' )
|
|
szPHost = szPHost+1;
|
|
|
|
// combine hash and path into one hash string
|
|
CookieToHash(szHash, szPHost, szPathBeg, ARRAY_ELEMENTS(szHash));
|
|
|
|
// search for the host-path combination
|
|
ndx = Hash_Find(pTheCookieJar, szHash, NULL, (void **)&pCookie);
|
|
|
|
if ( ndx != -1 )
|
|
{
|
|
// Yes, we found a match! Lets slam it into the headerlist
|
|
|
|
// but first lets STAMP IT since we're ACCESSING this cookie
|
|
pCookie->dcLast = dcTime;
|
|
|
|
// then we add it
|
|
AddCookieToHeader( pCookie, &dcTime, &pHeaderList, bIsSecure );
|
|
}
|
|
|
|
szLastHost = szPHost;
|
|
// now lets try parsing down to a more open path
|
|
szPHost = strchr( szPHost, '.' );
|
|
}
|
|
|
|
szLastPath = szPathEnd;
|
|
|
|
// if we've done the search for a root "/" slash already
|
|
// then stop, so we don't send the same cookie twice.
|
|
if ( szPathBeg[1] == '\0' )
|
|
break;
|
|
|
|
// ** now lets try parsing down to a more open path,
|
|
// cannot do this since this breaks with www.netscape.com
|
|
//szPathEnd = CookieParsePathBackwards(szPathBeg, szPathEnd);
|
|
|
|
szPathEnd--;
|
|
if ( szPathEnd != szPathBeg )
|
|
szPathEnd[0] = '\0';
|
|
}
|
|
|
|
// if we managed to create a header line, then lets
|
|
// add it to the Header
|
|
if ( pHeaderList )
|
|
{
|
|
HTHeaderList_Append(header, pHeaderList);
|
|
}
|
|
|
|
GTR_FREE(szPathBeg);
|
|
GTR_FREE(szHost);
|
|
return TRUE;
|
|
}
|
|
|
|
// PutThisInTheCookieJar - a worker rountine, called to add a cookie header to our CookieJar
|
|
// Also checks for "expired" cookies which signal a deletion of a cookie, otherwise
|
|
// the cookie is added or replaced as needed.
|
|
//
|
|
// headeritem - a header line with a Set-Cookie in it
|
|
// pTheCurDate - the current date/time found in the header
|
|
// szUrl - the URL we're adding from ( can be used to get host and path )
|
|
//
|
|
static VOID PutThisInTheCookieJar( HTHeaderList *headeritem, DCACHETIME *pTheCurDate, const char *szUrl )
|
|
{
|
|
struct CookieType *pNewCookie;
|
|
char *pszDomain, *pszPath;
|
|
char szHash[MAX_URL_STRING];
|
|
BOOL bDelMode;
|
|
|
|
ASSERT(headeritem);
|
|
ASSERT(pTheCurDate);
|
|
ASSERT(szUrl);
|
|
|
|
pNewCookie = HeaderToCookie(headeritem, &pszDomain, &pszPath);
|
|
|
|
if ( pNewCookie == NULL )
|
|
return;
|
|
|
|
// if there is no domain or path
|
|
// then get the default one.
|
|
|
|
if ( pszDomain == NULL )
|
|
pszDomain =
|
|
HTParse(szUrl, cszEmpString, PARSE_HOST);
|
|
else
|
|
pszDomain = GTR_strdup(pszDomain); // need to allocate since it points into header str
|
|
|
|
|
|
if ( pszDomain == NULL )
|
|
{
|
|
FreeCookie(pNewCookie); // if we still have NULL then remove it
|
|
return;
|
|
}
|
|
|
|
if ( pszPath == NULL)
|
|
pszPath =
|
|
HTParse(szUrl, cszEmpString, PARSE_PATH);
|
|
else
|
|
pszPath = GTR_strdup(pszPath); // need to allocate since it points into header str
|
|
|
|
|
|
if ( pszPath == NULL )
|
|
{
|
|
GTR_FREE(pszDomain);
|
|
FreeCookie(pNewCookie); // if we still have NULL then remove it
|
|
return;
|
|
}
|
|
|
|
|
|
// if there is no slash on the first character of the path, we need
|
|
// to put one there, since we depend on it in our seacrhes
|
|
// NOTE: this is done here not in HeaderToCookie since we may
|
|
// also have to handle the HTParse case
|
|
pszPath = SlamSlashOnToCookiePath( pszPath );
|
|
|
|
|
|
// if we failed somewhere in the parse, we give up.
|
|
if ( pszPath == NULL )
|
|
{
|
|
GTR_FREE(pszDomain);
|
|
FreeCookie(pNewCookie);
|
|
return;
|
|
}
|
|
|
|
CookieToHash( szHash, pszDomain, pszPath, ARRAY_ELEMENTS(szHash) );
|
|
|
|
// if we're given one that is stale, then its a death cookie aka Kamakazee
|
|
// ie we need to seek out and destory cookies that MATCH it exactly
|
|
bDelMode = IsCookieStale( pTheCurDate, pNewCookie, FALSE);
|
|
|
|
// STAMP THE COOKIE AS BEING ACCESSED, since we're adding/or/replacing it!
|
|
pNewCookie->dcLast = *pTheCurDate;
|
|
|
|
UpdateCookieJar(pNewCookie, szHash, bDelMode);
|
|
|
|
// now free the domain and path
|
|
GTR_FREE(pszDomain);
|
|
GTR_FREE(pszPath);
|
|
}
|
|
|
|
|
|
|
|
// x_ExtractSetCookieHeaders - extern func that is called to parse
|
|
// an incomming HTTP header to check for any "Set-Cookie"
|
|
// lines. If it finds any they will be added to our "CookieJar"
|
|
//
|
|
// header - incomming header to check for Set-Cookie
|
|
// szUrl - a string containing the URL we are connected with
|
|
//
|
|
VOID x_ExtractSetCookieHeaders( HTHeader *header, const char *szUrl)
|
|
{
|
|
DCACHETIME dcCurDate;
|
|
HTHeaderList *h1, *pDateHeader;
|
|
BOOL fHaveSetCookie = FALSE;
|
|
|
|
if ( header == NULL )
|
|
return;
|
|
|
|
ASSERT(szUrl);
|
|
|
|
h1 = HTHeaderList_FindFirstHeader(header, cszSetCookie);
|
|
|
|
if ( h1 )
|
|
{
|
|
// grab my cur date from the server, only if we have a cookie(s)
|
|
pDateHeader = HTHeaderList_FindFirstHeader(header, cszDate);
|
|
|
|
if ( pDateHeader && pDateHeader->value )
|
|
{
|
|
if ( ! FParseDate(&dcCurDate,pDateHeader->value) )
|
|
SetDCacheTime(&dcCurDate);
|
|
}
|
|
|
|
}
|
|
|
|
// we got our date lets see if it has any cookies.
|
|
|
|
while ( h1 )
|
|
{
|
|
// ahh-ha, we got a cookie lets see if its any good?
|
|
PutThisInTheCookieJar(h1, &dcCurDate, szUrl );
|
|
|
|
// next cookie?
|
|
h1 = HTHeaderList_FindNextHeader(h1, cszSetCookie);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#endif // ifdef COOKIES
|