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.
793 lines
20 KiB
793 lines
20 KiB
/*===================================================================
|
|
Microsoft IIS 5.0 (ASP)
|
|
|
|
Microsoft Confidential.
|
|
Copyright 1998 Microsoft Corporation. All Rights Reserved.
|
|
|
|
Component: 449 negotiations w/IE
|
|
|
|
File: ie449.cpp
|
|
|
|
Owner: DmitryR
|
|
|
|
This file contains the implementation of the 449 negotiations w/IE
|
|
===================================================================*/
|
|
#include "denpre.h"
|
|
#pragma hdrstop
|
|
|
|
#include "ie449.h"
|
|
#include "memchk.h"
|
|
|
|
/*===================================================================
|
|
Globals
|
|
===================================================================*/
|
|
|
|
C449FileMgr *m_p449FileMgr = NULL;
|
|
|
|
/*===================================================================
|
|
Internal funcitons
|
|
===================================================================*/
|
|
inline BOOL FindCookie
|
|
(
|
|
char *szCookiesBuf,
|
|
char *szCookie,
|
|
DWORD cbCookie
|
|
)
|
|
{
|
|
char *pch = szCookiesBuf;
|
|
if (pch == NULL || *pch == '\0')
|
|
return FALSE;
|
|
|
|
while (1)
|
|
{
|
|
if (strnicmp(pch, szCookie, cbCookie) == 0)
|
|
{
|
|
if (pch[cbCookie] == '=') // next char must be '='
|
|
return TRUE;
|
|
}
|
|
|
|
// next cookie
|
|
pch = strchr(pch, ';');
|
|
if (pch == NULL)
|
|
break;
|
|
while (*(++pch) == ' ') // skip ; and any spaces
|
|
;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*===================================================================
|
|
The API
|
|
===================================================================*/
|
|
|
|
/*===================================================================
|
|
Init449
|
|
===================================================================*/
|
|
HRESULT Init449()
|
|
{
|
|
// init hash table
|
|
m_p449FileMgr = new C449FileMgr;
|
|
if (m_p449FileMgr == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
HRESULT hr = m_p449FileMgr->Init();
|
|
if (FAILED(hr))
|
|
{
|
|
delete m_p449FileMgr;
|
|
m_p449FileMgr = NULL;
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
UnInit449
|
|
===================================================================*/
|
|
HRESULT UnInit449()
|
|
{
|
|
if (m_p449FileMgr != NULL)
|
|
{
|
|
delete m_p449FileMgr;
|
|
m_p449FileMgr = NULL;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
Create449Cookie
|
|
|
|
Get an existing 449 cookie from cache or create a new one
|
|
|
|
Parameters
|
|
szName cookie name
|
|
szFile script file
|
|
pp449 [out] the cookie
|
|
|
|
Returns
|
|
HRESULT
|
|
===================================================================*/
|
|
HRESULT Create449Cookie
|
|
(
|
|
char *szName,
|
|
TCHAR *szFile,
|
|
C449Cookie **pp449
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// Get the file first
|
|
C449File *pFile = NULL;
|
|
hr = m_p449FileMgr->GetFile(szFile, &pFile);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
// Create the cookie
|
|
hr = C449Cookie::Create449Cookie(szName, pFile, pp449);
|
|
if (FAILED(hr))
|
|
pFile->Release(); // GetFile gave it addref'd
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
Do449Processing
|
|
|
|
Check
|
|
if the browser is IE5+
|
|
there's no echo-reply: header
|
|
all the cookies are present
|
|
|
|
Construct and send 449 response if needed
|
|
|
|
When the response is sent, HitObj is marked as 'done with session'
|
|
|
|
Parameters
|
|
pHitObj the request
|
|
rgpCookies array of cookie requirements
|
|
cCookies number of cookie requirements
|
|
|
|
Returns
|
|
HRESULT
|
|
===================================================================*/
|
|
HRESULT Do449Processing
|
|
(
|
|
CHitObj *pHitObj,
|
|
C449Cookie **rgpCookies,
|
|
DWORD cCookies
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (cCookies == 0)
|
|
return hr;
|
|
|
|
//////////////////////////////////////////
|
|
// check the browser
|
|
|
|
BOOL fBrowser = FALSE;
|
|
char *szBrowser = pHitObj->PIReq()->QueryPszUserAgent();
|
|
if (szBrowser == NULL || szBrowser[0] == '\0')
|
|
return S_OK; // bad browser
|
|
|
|
char *szMSIE = strstr(szBrowser, "MSIE ");
|
|
if (szMSIE)
|
|
{
|
|
char chVersion = szMSIE[5];
|
|
if (chVersion >= '5' && chVersion <= '9')
|
|
fBrowser = TRUE;
|
|
}
|
|
|
|
#ifdef TINYGET449
|
|
if (strcmp(szBrowser, "WBCLI") == 0)
|
|
fBrowser = TRUE;
|
|
#endif
|
|
|
|
if (!fBrowser) // bad browser
|
|
return S_OK;
|
|
|
|
//////////////////////////////////////////
|
|
// check the cookies
|
|
|
|
char *szCookie = pHitObj->PIReq()->QueryPszCookie();
|
|
|
|
// collect the arrays of pointers and sizes for not found cookies.
|
|
// arrays size to at most number of cookies in the template.
|
|
DWORD cNotFound = 0;
|
|
DWORD cbNotFound = 0;
|
|
STACK_BUFFER( tempCookies, 128 );
|
|
|
|
if (!tempCookies.Resize(cCookies * sizeof(WSABUF))) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
LPWSABUF rgpNotFound = (LPWSABUF)tempCookies.QueryPtr();
|
|
|
|
for (DWORD i = 0; SUCCEEDED(hr) && (i < cCookies); i++)
|
|
{
|
|
if (!FindCookie(szCookie, rgpCookies[i]->SzCookie(), rgpCookies[i]->CbCookie()))
|
|
{
|
|
// cookie not found -- append to the list
|
|
|
|
hr = rgpCookies[i]->LoadFile();
|
|
if (SUCCEEDED(hr)) // ignore bad files
|
|
{
|
|
rgpNotFound[cNotFound].buf = rgpCookies[i]->SzBuffer();
|
|
rgpNotFound[cNotFound].len = rgpCookies[i]->CbBuffer();
|
|
cbNotFound += rgpCookies[i]->CbBuffer();
|
|
cNotFound++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!SUCCEEDED(hr))
|
|
return hr;
|
|
|
|
if (cNotFound == 0)
|
|
return S_OK; // everything's found
|
|
|
|
//////////////////////////////////////////
|
|
// check echo-reply header
|
|
|
|
char szEcho[80];
|
|
DWORD dwEchoLen = sizeof(szEcho);
|
|
if (pHitObj->PIReq()->GetServerVariableA("HTTP_MS_ECHO_REPLY", szEcho, &dwEchoLen)
|
|
|| GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
return S_OK; // already in response cycle
|
|
}
|
|
|
|
//////////////////////////////////////////
|
|
// send the 449 response
|
|
|
|
CResponse::WriteBlocksResponse
|
|
(
|
|
pHitObj->PIReq(), // WAM_EXEC_INFO
|
|
cNotFound, // number of blocks
|
|
rgpNotFound, // array of blocks
|
|
cbNotFound, // total number of bytes in blocks
|
|
NULL, // text/html
|
|
"449 Retry with", // status
|
|
"ms-echo-request: execute opaque=\"0\" location=\"BODY\"\r\n" // extra header
|
|
);
|
|
|
|
//////////////////////////////////////////
|
|
// tell HitObj no to write anything else
|
|
|
|
pHitObj->SetDoneWithSession();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
Do449ChangeNotification
|
|
|
|
Change notification processing
|
|
|
|
Parameters
|
|
szFile file changed or NULL for all
|
|
|
|
Returns
|
|
HRESULT
|
|
===================================================================*/
|
|
HRESULT Do449ChangeNotification
|
|
(
|
|
TCHAR *szFile
|
|
)
|
|
{
|
|
// if m_p449FileMgr is NULL, we're likely getting called after
|
|
// the 449 manager has been UnInited. Return S_OK in this case.
|
|
|
|
if (m_p449FileMgr == NULL)
|
|
return S_OK;
|
|
|
|
if (szFile)
|
|
return m_p449FileMgr->Flush(szFile);
|
|
else
|
|
return m_p449FileMgr->FlushAll();
|
|
}
|
|
|
|
|
|
/*===================================================================
|
|
Class C449File
|
|
===================================================================*/
|
|
|
|
/*===================================================================
|
|
C449File::C449File
|
|
|
|
Constructor
|
|
===================================================================*/
|
|
C449File::C449File()
|
|
{
|
|
m_cRefs = 0;
|
|
m_fNeedLoad = 1;
|
|
m_szFile = NULL;
|
|
m_szBuffer = NULL;
|
|
m_cbBuffer = 0;
|
|
m_pDME = NULL;
|
|
m_hFileReadyForUse=NULL;
|
|
m_hrLoadResult= E_FAIL;
|
|
}
|
|
|
|
/*===================================================================
|
|
C449File::~C449File
|
|
|
|
Destructor
|
|
===================================================================*/
|
|
C449File::~C449File()
|
|
{
|
|
Assert(m_cRefs == 0);
|
|
if (m_szFile)
|
|
free(m_szFile);
|
|
if (m_szBuffer)
|
|
free(m_szBuffer);
|
|
if (m_pDME)
|
|
m_pDME->Release();
|
|
if(m_hFileReadyForUse != NULL)
|
|
CloseHandle(m_hFileReadyForUse);
|
|
}
|
|
|
|
/*===================================================================
|
|
C449File::Init
|
|
|
|
Init strings,
|
|
Load file for the first time,
|
|
Start change notifications
|
|
===================================================================*/
|
|
HRESULT C449File::Init
|
|
(
|
|
TCHAR *szFile
|
|
)
|
|
{
|
|
// remember the name
|
|
m_szFile = StringDup(szFile);
|
|
if (m_szFile == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
// init link element
|
|
CLinkElem::Init(m_szFile, _tcslen(m_szFile)*sizeof(TCHAR));
|
|
|
|
// Create event: manual-reset, ready-for-use event; non-signaled
|
|
// Better create event before using it.
|
|
m_hFileReadyForUse = IIS_CREATE_EVENT(
|
|
"C449File::m_hFileReadyForUse",
|
|
this,
|
|
TRUE, // flag for manual-reset event
|
|
FALSE // flag for initial state
|
|
);
|
|
if (!m_hFileReadyForUse)
|
|
return E_OUTOFMEMORY;
|
|
|
|
// load
|
|
m_fNeedLoad = 1;
|
|
HRESULT hr = Load();
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
// start directory notifications
|
|
TCHAR *pch = _tcsrchr(m_szFile, _T('\\')); // last backslash
|
|
if (pch == NULL)
|
|
return E_FAIL; // bogus filename?
|
|
CASPDirMonitorEntry *pDME = NULL;
|
|
*pch = _T('\0');
|
|
RegisterASPDirMonitorEntry(m_szFile, &pDME);
|
|
*pch = _T('\\');
|
|
m_pDME = pDME;
|
|
|
|
// done
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
C449File::Load
|
|
|
|
Load the file if needed
|
|
===================================================================*/
|
|
HRESULT C449File::Load()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
HANDLE hMap = NULL;
|
|
BYTE *pbBytes = NULL;
|
|
DWORD dwSize = 0;
|
|
|
|
// check if this thread needs to load the file
|
|
if (InterlockedExchange(&m_fNeedLoad, 0) == 0)
|
|
{
|
|
WaitForSingleObject(m_hFileReadyForUse, INFINITE);
|
|
return m_hrLoadResult;
|
|
}
|
|
|
|
|
|
// cleanup the existing data if any
|
|
if (m_szBuffer)
|
|
free(m_szBuffer);
|
|
m_szBuffer = NULL;
|
|
m_cbBuffer = 0;
|
|
|
|
// open the file
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hFile = AspCreateFile(
|
|
m_szFile,
|
|
GENERIC_READ, // access (read-write) mode
|
|
FILE_SHARE_READ, // share mode
|
|
NULL, // pointer to security descriptor
|
|
OPEN_EXISTING, // how to create
|
|
FILE_ATTRIBUTE_NORMAL, // file attributes
|
|
NULL // handle to file with attributes to copy
|
|
);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
// get file size
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
dwSize = GetFileSize(hFile, NULL);
|
|
if (dwSize == 0 || dwSize == 0xFFFFFFFF)
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
// create mapping
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hMap = CreateFileMapping(
|
|
hFile, // handle to file to map
|
|
NULL, // optional security attributes
|
|
PAGE_READONLY, // protection for mapping object
|
|
0, // high-order 32 bits of object size
|
|
0, // low-order 32 bits of object size
|
|
NULL // name of file-mapping object
|
|
);
|
|
if (hMap == NULL)
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
// map the file
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
pbBytes = (BYTE *)MapViewOfFile(
|
|
hMap, // file-mapping object to map into address space
|
|
FILE_MAP_READ, // access mode
|
|
0, // high-order 32 bits of file offset
|
|
0, // low-order 32 bits of file offset
|
|
0 // number of bytes to map
|
|
);
|
|
if (pbBytes == NULL)
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
// remember the bytes
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_szBuffer = (char *)malloc(dwSize);
|
|
if (m_szBuffer != NULL)
|
|
{
|
|
memcpy(m_szBuffer, pbBytes, dwSize);
|
|
m_cbBuffer = dwSize;
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
// cleanup
|
|
if (pbBytes != NULL)
|
|
UnmapViewOfFile(pbBytes);
|
|
if (hMap != NULL)
|
|
CloseHandle(hMap);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
CloseHandle(hFile);
|
|
|
|
// Set the Need Load flag or flag the read event as successful.
|
|
m_hrLoadResult = hr;
|
|
SetEvent(m_hFileReadyForUse);
|
|
|
|
return m_hrLoadResult;
|
|
}
|
|
|
|
/*===================================================================
|
|
C449File::Create449File
|
|
|
|
static constructor
|
|
===================================================================*/
|
|
HRESULT C449File::Create449File
|
|
(
|
|
TCHAR *szFile,
|
|
C449File **ppFile
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
C449File *pFile = new C449File;
|
|
if (pFile == NULL)
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pFile->Init(szFile);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
pFile->AddRef();
|
|
*ppFile = pFile;
|
|
}
|
|
else if (pFile)
|
|
{
|
|
delete pFile;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
C449File::QueryInterface
|
|
C449File::AddRef
|
|
C449File::Release
|
|
|
|
IUnknown members for C449File object.
|
|
===================================================================*/
|
|
STDMETHODIMP C449File::QueryInterface(REFIID riid, VOID **ppv)
|
|
{
|
|
// should never be called
|
|
Assert(FALSE);
|
|
*ppv = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) C449File::AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_cRefs);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) C449File::Release()
|
|
{
|
|
LONG cRefs = InterlockedDecrement(&m_cRefs);
|
|
if (cRefs)
|
|
return cRefs;
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*===================================================================
|
|
Class C449FileMgr
|
|
===================================================================*/
|
|
|
|
/*===================================================================
|
|
C449FileMgr::C449FileMgr
|
|
|
|
Constructor
|
|
===================================================================*/
|
|
C449FileMgr::C449FileMgr()
|
|
{
|
|
INITIALIZE_CRITICAL_SECTION(&m_csLock);
|
|
}
|
|
|
|
/*===================================================================
|
|
C449FileMgr::~C449FileMgr
|
|
|
|
Destructor
|
|
===================================================================*/
|
|
C449FileMgr::~C449FileMgr()
|
|
{
|
|
FlushAll();
|
|
m_ht449Files.UnInit();
|
|
DeleteCriticalSection(&m_csLock);
|
|
}
|
|
|
|
/*===================================================================
|
|
C449FileMgr::Init
|
|
|
|
Initialization
|
|
===================================================================*/
|
|
HRESULT C449FileMgr::Init()
|
|
{
|
|
return m_ht449Files.Init(199);
|
|
}
|
|
|
|
/*===================================================================
|
|
C449FileMgr::GetFile
|
|
|
|
Find file in the hash table, or create a new one
|
|
===================================================================*/
|
|
HRESULT C449FileMgr::GetFile
|
|
(
|
|
TCHAR *szFile,
|
|
C449File **ppFile
|
|
)
|
|
{
|
|
C449File *pFile = NULL;
|
|
CLinkElem *pElem;
|
|
|
|
Lock();
|
|
|
|
pElem = m_ht449Files.FindElem(szFile, _tcslen(szFile)*sizeof(TCHAR));
|
|
|
|
if (pElem)
|
|
{
|
|
// found
|
|
pFile = static_cast<C449File *>(pElem);
|
|
if (!SUCCEEDED(pFile->Load()))
|
|
pFile = NULL;
|
|
else
|
|
pFile->AddRef(); // 1 ref to hand out
|
|
}
|
|
else if (SUCCEEDED(C449File::Create449File(szFile, &pFile)))
|
|
{
|
|
if (m_ht449Files.AddElem(pFile))
|
|
pFile->AddRef(); // 1 for hash table + 1 to hand out
|
|
}
|
|
|
|
UnLock();
|
|
|
|
*ppFile = pFile;
|
|
return (pFile != NULL) ? S_OK : E_FAIL;
|
|
}
|
|
|
|
/*===================================================================
|
|
C449FileMgr::Flush
|
|
|
|
Change notification for a single file
|
|
===================================================================*/
|
|
HRESULT C449FileMgr::Flush
|
|
(
|
|
TCHAR *szFile
|
|
)
|
|
{
|
|
Lock();
|
|
|
|
CLinkElem *pElem = m_ht449Files.FindElem(szFile, _tcslen(szFile)*sizeof(TCHAR));
|
|
if (pElem)
|
|
{
|
|
C449File *pFile = static_cast<C449File *>(pElem);
|
|
pFile->SetNeedLoad(); // next time reload
|
|
}
|
|
|
|
UnLock();
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
C449FileMgr::FlushAll
|
|
|
|
Remove all files
|
|
FlushAll is always together with template flush
|
|
===================================================================*/
|
|
HRESULT C449FileMgr::FlushAll()
|
|
{
|
|
// Unlink from hash table first
|
|
Lock();
|
|
CLinkElem *pElem = m_ht449Files.Head();
|
|
m_ht449Files.ReInit();
|
|
UnLock();
|
|
|
|
// Walk the list to remove all
|
|
while (pElem)
|
|
{
|
|
C449File *pFile = static_cast<C449File *>(pElem);
|
|
pElem = pElem->m_pNext;
|
|
pFile->Release();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
Class C449Cookie
|
|
===================================================================*/
|
|
|
|
/*===================================================================
|
|
C449Cookie::C449Cookie
|
|
|
|
Constructor
|
|
===================================================================*/
|
|
C449Cookie::C449Cookie()
|
|
{
|
|
m_cRefs = 0;
|
|
m_szName = NULL;
|
|
m_cbName = 0;
|
|
m_pFile = NULL;
|
|
}
|
|
|
|
/*===================================================================
|
|
C449Cookie::~C449Cookie
|
|
|
|
Destructor
|
|
===================================================================*/
|
|
C449Cookie::~C449Cookie()
|
|
{
|
|
Assert(m_cRefs == 0);
|
|
if (m_szName)
|
|
free(m_szName);
|
|
if (m_pFile)
|
|
m_pFile->Release();
|
|
}
|
|
|
|
/*===================================================================
|
|
C449Cookie::Init
|
|
|
|
Initialize
|
|
===================================================================*/
|
|
HRESULT C449Cookie::Init
|
|
(
|
|
char *szName,
|
|
C449File *pFile
|
|
)
|
|
{
|
|
m_szName = StringDupA(szName);
|
|
if (m_szName == NULL)
|
|
return E_OUTOFMEMORY;
|
|
m_cbName = strlen(m_szName);
|
|
|
|
m_pFile = pFile;
|
|
return S_OK;
|
|
}
|
|
|
|
/*===================================================================
|
|
C449Cookie::Create449Cookie
|
|
|
|
static constructor
|
|
===================================================================*/
|
|
HRESULT C449Cookie::Create449Cookie
|
|
(
|
|
char *szName,
|
|
C449File *pFile,
|
|
C449Cookie **pp449Cookie
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
C449Cookie *pCookie = new C449Cookie;
|
|
if (pCookie == NULL)
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pCookie->Init(szName, pFile);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
pCookie->AddRef();
|
|
*pp449Cookie = pCookie;
|
|
}
|
|
else if (pCookie)
|
|
{
|
|
delete pCookie;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*===================================================================
|
|
C449Cookie::QueryInterface
|
|
C449Cookie::AddRef
|
|
C449Cookie::Release
|
|
|
|
IUnknown members for C449Cookie object.
|
|
===================================================================*/
|
|
STDMETHODIMP C449Cookie::QueryInterface(REFIID riid, VOID **ppv)
|
|
{
|
|
// should never be called
|
|
Assert(FALSE);
|
|
*ppv = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) C449Cookie::AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_cRefs);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) C449Cookie::Release()
|
|
{
|
|
LONG cRefs = InterlockedDecrement(&m_cRefs);
|
|
if (cRefs)
|
|
return cRefs;
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|