2025-04-27 07:49:33 -04:00

774 lines
20 KiB
C++

/*
** ADROT.CPP
** Sean P. Nolan
**
** Ad Rotator SSO Real Work
Abstract:
Reads in an advertising schedule file and creates html for displaying
advertising images based on the "percentages" configured in that file.
Authors:
seanp Sean P. Nolan
davidme David Messner
History
~02/xx/96 seanp
Created
07/25/96 davidme
Object taken over by the advertising group within MSN.
Original adrot.dll version at time of split: 1.0.0.6
No longer an arbitrary number of sched file entries
An href field consisting of a single character now implies that the
image should not be href'ed
Use 32 bit ints instead of 16 for the ad "percentage" values; had to
use floating point math to avoid overflow when determining random ad
Change to wcsutil.cpp fixing rename notification bug so that when schedule
file is renamed, it forces schedule to be flushed from memory
Add optional attributes .Clickable, .Border, .FrameTarget
*/
#pragma warning(disable: 4237) // disable "bool" reserved
#include "ssobase.h"
#include "adrot.h"
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
/*--------------------------------------------------------------------------+
| Local Prototypes |
+--------------------------------------------------------------------------*/
HRESULT SSOGetAdvertisement(WORD wInvokeFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, SSSTUFF *pstuff);
HRESULT SSOClickable(WORD wInvokeFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, SSSTUFF *pstuff);
HRESULT SSOTargetFrame(WORD wInvokeFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, SSSTUFF *pstuff);
HRESULT SSOBorder(WORD wInvokeFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, SSSTUFF *pstuff);
HRESULT SSOAbout(WORD wInvokeFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, SSSTUFF *pstuff);
/*--------------------------------------------------------------------------+
| Globals We Have To Provide |
+--------------------------------------------------------------------------*/
SSOMETHOD g_rgssomethod[] = { { L"GetAdvertisement",
(PFNSSOMETHOD) SSOGetAdvertisement,
0 },
{ L"Clickable",
(PFNSSOMETHOD) SSOClickable,
0 },
{ L"Border",
(PFNSSOMETHOD) SSOBorder,
0 },
{ L"TargetFrame",
(PFNSSOMETHOD) SSOTargetFrame,
0 },
{ L"About",
(PFNSSOMETHOD) SSOAbout,
0 },
{ NULL,
NULL,
0 }};
PFNSSOMETHOD g_pfnssoDynamic = NULL;
LPSTR g_szSSOProgID = "MSWC.AdRotator";
BOOL g_fPersistentSSO = TRUE;
GUID CDECL g_clsidSSO =
{ 0x1621f7c0, 0x60ac, 0x11cf,
{ 0x94, 0x27, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00 } };
/*--------------------------------------------------------------------------+
| Constants |
+--------------------------------------------------------------------------*/
#define CAE_NIL (0xFFFF)
#define DXP_AD_DEFAULT (440)
#define DYP_AD_DEFAULT (60)
#define HSPACE_AD_DEFAULT (0)
#define VSPACE_AD_DEFAULT (0)
#define DP_AD_BORDER_DEFAULT (1)
#define DP_BORDER_UNSPECIFIED (0xFF)
const char *c_szHrefFormat = "<A HREF=\"%s?url=%s&image=%s\" %s><IMG SRC=\"%s\" ALT=\"%s\" WIDTH=%d HEIGHT=%d %s %s BORDER=%d></A>";
const char *c_szHtmlFormat = "<IMG SRC=\"%s\" ALT=\"%s\" WIDTH=%d HEIGHT=%d %s %s BORDER=%d>";
const char *c_szHeaderHeight = "HEIGHT";
const char *c_szHeaderWidth = "WIDTH";
const char *c_szHeaderBorder = "BORDER";
const char *c_szHeaderRedirect = "REDIRECT";
const char *c_szHeaderHspace = "HSPACE";
const char *c_szHeaderVspace = "VSPACE";
/*--------------------------------------------------------------------------+
| CAdRotator |
+--------------------------------------------------------------------------*/
#define MAX_HTML_LENGTH (2048) // maximum length of entire html output
#define MAX_URL_LENGTH (256) // storage for Link and Gif Image attributes
#define MAX_ALT_LENGTH (128) // storage for Alt text for image
#define AD_BLOCK_SIZE (16) // Allocation block size (in Ad Entries)
#define DEFAULTSTRSIZE 1024 // size of localized text string
HANDLE g_hAdHeap; // Heap for storing the Ad Entry Lists
typedef struct _AdElement
{
ULONG Percentage;
CHAR szLink[2*MAX_URL_LENGTH];
CHAR szGif[2*MAX_URL_LENGTH];
CHAR szAlt[2*MAX_ALT_LENGTH];
}
AE;
class CAdRotator : public CDataFile
{
private:
USHORT m_cae;
AE *m_rgae;
ULONG m_PercentageTotal;
USHORT m_dxp;
USHORT m_dyp;
USHORT m_dpBorder;
CHAR m_szAdRedirector[MAX_PATH];
CHAR m_szHspace[25];
CHAR m_szVspace[25];
public:
CAdRotator(LPSTR szDataPath, CDataFileGroup *pdfg);
~CAdRotator(void);
HRESULT GetAdvertisement(BSTR *pbstrResult, SSSTUFF *pstuff);
virtual void FreeDataFile(void) { delete this; }
private:
HRESULT BuildAEList(void);
LPSTR PchReadHeaders(LPSTR sz);
BSTR FormatAdvertisement(AE *pae, SSSTUFF *pstuff);
};
/*--------------------------------------------------------------------------+
| CAdRotGroup |
+--------------------------------------------------------------------------*/
class CAdRotGroup : public CDataFileGroup
{
public:
virtual CDataFile *CreateDataFile(LPSTR szDataPath)
{ return((CDataFile*)(new CAdRotator(szDataPath, this))); }
};
CAdRotGroup g_dfg;
/*--------------------------------------------------------------------------+
| AdVariant |
+--------------------------------------------------------------------------*/
// These attributes are controllable from the content. Advariant structures
// maintain state information associated with an instance of a script rather
// than with an instance of an AdRotator, which might serve many scripts
//
typedef struct _AdVariant
{
BOOL fClickable;
USHORT dpBorder;
CHAR szTarget[MAX_URL_LENGTH];
}
ADVARIANT;
/*--------------------------------------------------------------------------+
| CAdRotator |
+--------------------------------------------------------------------------*/
CAdRotator::CAdRotator(LPSTR szDataPath, CDataFileGroup *pdfg)
: CDataFile(szDataPath, pdfg)
{
m_dxp = DXP_AD_DEFAULT;
m_dyp = DYP_AD_DEFAULT;
m_dpBorder = DP_AD_BORDER_DEFAULT;
m_szAdRedirector[0] = 0;
m_rgae = NULL;
m_cae = CAE_NIL;
}
CAdRotator::~CAdRotator()
{
// Free the advertising entry list that was allocated in BuildAEList
if (m_rgae && g_hAdHeap)
::HeapFree(g_hAdHeap, 0, m_rgae);
#ifdef DEBUG
::FillMemory(this, sizeof(this), 0xAC);
#endif
}
LPSTR
CAdRotator::PchReadHeaders(LPSTR sz)
{
LPSTR pch = sz;
LPSTR pchWalk, pchValue;
DWORD dxpHspace = HSPACE_AD_DEFAULT;
DWORD dypVspace = VSPACE_AD_DEFAULT;
while (*pch && *pch != '*')
{
// terminate this line
pchWalk = _FindEndOfLine(pch);
if (*pchWalk)
*pchWalk++ = 0;
// deal with this header line
for (pchValue = pch; *pchValue && *pchValue != ' ' && *pchValue != '\t'; pchValue = CharNext(pchValue))
/* nothing */ ;
if (*pchValue)
{
*pchValue++ = 0;
while (*pchValue && (*pchValue == ' ' || *pchValue == '\t'))
pchValue = CharNext(pchValue);
if (*pchValue)
{
// ok! we have pch pointing to the name and pchValue pointing
// at the value... hoooooray!
if (!lstrcmpi(pch, c_szHeaderHeight))
{
m_dyp = (USHORT) _atoi(pchValue);
}
else if (!lstrcmpi(pch, c_szHeaderWidth))
{
m_dxp = (USHORT) _atoi(pchValue);
}
else if (!lstrcmpi(pch, c_szHeaderHspace))
{
dxpHspace = (USHORT) _atoi(pchValue);
}
else if (!lstrcmpi(pch, c_szHeaderVspace))
{
dypVspace = (USHORT) _atoi(pchValue);
}
else if (!lstrcmpi(pch, c_szHeaderBorder))
{
m_dpBorder = (USHORT) _atoi(pchValue);
}
else if (!lstrcmpi(pch, c_szHeaderRedirect))
{
lstrcpy(m_szAdRedirector, pchValue);
}
else
{
ExceptionId(g_clsidSSO, SSO_ADROT, SSO_UNKNOWN_HEADER_NAME);
}
}
}
else
ExceptionId(g_clsidSSO, SSO_ADROT, SSO_HEADER_HAS_NO_ASSOCIATED_VALUE);
// advance to next line
pch = _SkipWhiteSpace(pchWalk);
}
// Format the Hspace and Vspace attributes for this rotator instance
if (dxpHspace)
wsprintf(m_szHspace, "HSPACE=%d", dxpHspace);
else
m_szHspace[0] = 0;
if (dypVspace)
wsprintf(m_szVspace, "VSPACE=%d", dypVspace);
else
m_szVspace[0] = 0;
pch = _FindEndOfLine(pch);
return(pch);
}
HRESULT
CAdRotator::BuildAEList()
{
LPSTR pch;
LPSTR szGif, szLink, szAlt, szPercent;
AE *pae;
HANDLE hfile = INVALID_HANDLE_VALUE;
LPSTR szData = NULL;
HRESULT hr;
DWORD cb, cbRead;
DWORD dwBlocks = 1;
// this happens when there was a problem loading the file
if (!m_cae)
{
hr = E_FAIL;
goto LBuildRet;
}
// already loaded ok
if (m_cae != CAE_NIL)
{
hr = NOERROR;
goto LBuildRet;
}
// hasn't been loaded yet ... try to load the file into memory
hfile = ::CreateFile(m_szDataPath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hfile == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_WIN32(::GetLastError());
ExceptionId(g_clsidSSO, SSO_ADROT, SSO_CANNOT_LOAD_ROTATION_SCHEDULE_FILE);
goto LBuildRet;
}
// allocate memory for the advertising entry list
if (!(m_rgae = (AE *)::HeapAlloc(g_hAdHeap, 0, sizeof(AE) * AD_BLOCK_SIZE)))
{
hr = E_OUTOFMEMORY;
goto LBuildRet;
}
if ((cb = ::GetFileSize(hfile, NULL)) == 0xFFFFFFFF)
{
hr = HRESULT_FROM_WIN32(::GetLastError());
goto LBuildRet;
}
if (!(szData = (LPSTR) ::HeapAlloc(::GetProcessHeap(), 0, cb + 1)))
{
hr = HRESULT_FROM_WIN32(::GetLastError());
goto LBuildRet;
}
if (!::ReadFile(hfile, szData, cb, &cbRead, NULL))
{
hr = HRESULT_FROM_WIN32(::GetLastError());
goto LBuildRet;
}
szData[cb] = 0;
// ok, so we've got a pointer to it ... first read the header info
pch = this->PchReadHeaders(szData);
if (!pch || !*pch)
{
hr = E_FAIL;
ExceptionId(g_clsidSSO, SSO_ADROT, SSO_CANNOT_READ_ROTATION_SCHEDULE_FILE);
goto LBuildRet;
}
// now read in the entries
m_PercentageTotal = 0;
for (m_cae = 0, pae = m_rgae; *pch; ++m_cae, ++pae)
{
// Memory is allocated incrementally as needed
// check to see if we've filled the last block and need to
// allocate a bigger chunk and adjust pointers.
if (m_cae == dwBlocks * AD_BLOCK_SIZE)
{
m_rgae = (AE *)::HeapReAlloc(g_hAdHeap, 0, m_rgae,
(dwBlocks+1) * AD_BLOCK_SIZE * sizeof(AE));
if (NULL == m_rgae)
{
hr = E_OUTOFMEMORY;
goto LBuildRet;
}
pae = m_rgae + AD_BLOCK_SIZE * dwBlocks++;
}
// get the url to the gif
szGif = _SkipWhiteSpace(pch);
pch = _FindEndOfLine(szGif);
if (*pch)
*pch++ = 0;
// get the url for the link
szLink = _SkipWhiteSpace(pch);
pch = _FindEndOfLine(szLink);
if (*pch)
*pch++ = 0;
// get the alt text
szAlt = _SkipWhiteSpace(pch);
pch = _FindEndOfLine(szAlt);
if (*pch)
*pch++ = 0;
// get the percentage count
szPercent = _SkipWhiteSpace(pch);
pch = _FindEndOfLine(szPercent);
if (*pch)
*pch++ = 0;
if (*szGif && *szLink && *szAlt && *szPercent)
{
// We now ignore entries with a zeroed "percentage" value
pae->Percentage = _atoi(szPercent);
if (0 == pae->Percentage)
{
--m_cae;
--pae;
continue;
}
m_PercentageTotal += pae->Percentage;
strncpy(pae->szGif, szGif, MAX_URL_LENGTH);
strncpy(pae->szLink, szLink, MAX_URL_LENGTH);
strncpy(pae->szAlt, szAlt, MAX_ALT_LENGTH);
}
else
{
// error out if we got a gif but not one of the others
if (*szGif)
{
m_cae = 0;
hr = E_FAIL;
goto LBuildRet;
}
else
break;
}
pch = _SkipWhiteSpace(pch);
}
// this happens when there was a problem loading the file
if (!m_cae)
{
hr = E_FAIL;
goto LBuildRet;
}
// everything went well ... register for change notifications
// on the file so that we can flush our cache appropriately
if (!this->FWatchFile())
{
hr = E_FAIL;
goto LBuildRet;
}
hr = NOERROR;
LBuildRet:
if (szData)
::HeapFree(::GetProcessHeap(), 0, szData);
if (hfile != INVALID_HANDLE_VALUE)
::CloseHandle(hfile);
return(hr);
}
BSTR CAdRotator::FormatAdvertisement(AE *pae, SSSTUFF *pstuff)
{
ADVARIANT *padVariant;
CHAR szHtml[MAX_HTML_LENGTH];
BSTR bstr;
DWORD cbHtml;
CHAR *pszTarget = "";
BOOL fClickable;
USHORT dpBorder = m_dpBorder;
if (padVariant = (ADVARIANT *)pstuff->lUser)
{
fClickable = (*((pae->szLink)+1) && padVariant->fClickable);
pszTarget = padVariant->szTarget;
if (padVariant->dpBorder != DP_BORDER_UNSPECIFIED)
dpBorder = padVariant->dpBorder;
}
else
{
dpBorder = m_dpBorder;
fClickable = (*((pae->szLink)+1));
}
if (fClickable) // if the link is > 1 character, href the image
wsprintf(szHtml, c_szHrefFormat, m_szAdRedirector,
pae->szLink, pae->szGif, pszTarget, pae->szGif,
pae->szAlt, m_dxp, m_dyp,
m_szHspace, m_szVspace, dpBorder);
else // otherwise display only the image
wsprintf(szHtml, c_szHtmlFormat,
pae->szGif, pae->szAlt, m_dxp, m_dyp,
m_szHspace, m_szVspace, dpBorder);
// Allocate a BSTR for passing back the result
cbHtml = strlen(szHtml);
if (!(bstr = SysAllocStringLen(NULL, cbHtml)))
return NULL;
::MultiByteToWideChar(CP_ACP, 0, szHtml, -1, bstr, cbHtml);
bstr[cbHtml] = 0;
return bstr;
}
CCritSec g_csRand;
HRESULT
CAdRotator::GetAdvertisement(BSTR *pbstrResult, SSSTUFF *pstuff)
{
HRESULT hr;
ULONG Target, Current;
USHORT iae;
AE *pae;
m_cs.Lock();
if (SUCCEEDED(hr = this->BuildAEList()))
{
// Note that the random number is based
// off of m_PercentageTotal and NOT 100.
g_csRand.Lock();
// This ugly casting is needed to prevent overflow when large "percentages"
// are used
Target = (ULONG)(
((double)rand()/(double)RAND_MAX)*m_PercentageTotal);
g_csRand.Unlock();
pae = m_rgae;
iae = 0;
Current = pae->Percentage;
while (Current <= Target)
{
if (++iae == m_cae)
break;
++pae;
Current += pae->Percentage;
}
// heeeere we go!
*pbstrResult = this->FormatAdvertisement(pae, pstuff);
}
m_cs.Unlock();
return(hr);
}
ADVARIANT *GetAdVariant(SSSTUFF *pstuff)
{
ADVARIANT *padVariant;
if (pstuff->lUser)
padVariant = (ADVARIANT *)pstuff->lUser;
else
{
if (!(padVariant = (ADVARIANT *)new ADVARIANT))
return NULL;
// Set up defaults for the new state object
padVariant->fClickable = TRUE;
padVariant->dpBorder = DP_BORDER_UNSPECIFIED;
*(padVariant->szTarget) = '\0';
}
return padVariant;
}
/*--------------------------------------------------------------------------+
| External Interface |
+--------------------------------------------------------------------------*/
HRESULT
SSOGetAdvertisement(WORD wInvokeFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, SSSTUFF *pstuff)
{
HRESULT hr;
BSTR bstrResult;
CAdRotator *par = NULL;
char szPathTranslated[MAX_PATH];
if (pdispparams->cArgs == 0)
return(DISP_E_PARAMNOTOPTIONAL);
if (pdispparams->cArgs > 1)
return(DISP_E_PARAMNOTFOUND);
if (FAILED(hr = SSOTranslateVirtualRoot(pdispparams->rgvarg, pstuff->punk,
szPathTranslated,
sizeof(szPathTranslated))))
{
return(hr);
}
if (!(par = (CAdRotator*) g_dfg.GetDataFile(szPathTranslated)))
return(E_FAIL);
if (SUCCEEDED(hr = par->GetAdvertisement(&bstrResult, pstuff)))
{
::VariantInit(pvarResult);
pvarResult->vt = VT_BSTR;
V_BSTR(pvarResult) = bstrResult;
}
else
{
// Forget this file, there was an error.
//
// g_dfg.ForgetDataFile(par);
//
// BUG FIX: if ForgetDataFile is called under load it will cause
// a GP Fault, the code is not needed, the clean up is done on
// control release.
}
par->Release();
return(hr);
}
HRESULT
SSOClickable(WORD wInvokeFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, SSSTUFF *pstuff)
{
ADVARIANT *padVariant;
if (pdispparams->cArgs == 0)
return(DISP_E_PARAMNOTOPTIONAL);
if (pdispparams->cArgs > 1)
return(DISP_E_PARAMNOTFOUND);
if (!(padVariant = GetAdVariant(pstuff)))
return E_OUTOFMEMORY;
padVariant->fClickable = V_BOOL(pdispparams->rgvarg);
pstuff->lUser = (LONG)padVariant;
return NOERROR;
}
HRESULT
SSOBorder(WORD wInvokeFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, SSSTUFF *pstuff)
{
ADVARIANT *padVariant;
if (pdispparams->cArgs == 0)
return(DISP_E_PARAMNOTOPTIONAL);
if (pdispparams->cArgs > 1)
return(DISP_E_PARAMNOTFOUND);
if (!(padVariant = GetAdVariant(pstuff)))
return E_OUTOFMEMORY;
padVariant->dpBorder = V_I2(pdispparams->rgvarg);
pstuff->lUser = (LONG)padVariant;
return NOERROR;
}
HRESULT
SSOTargetFrame(WORD wInvokeFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, SSSTUFF *pstuff)
{
ADVARIANT *padVariant;
BSTR szwFrame;
UINT cch;
CHAR szBuf[MAX_URL_LENGTH];
if (pdispparams->cArgs == 0)
return(DISP_E_PARAMNOTOPTIONAL);
if (pdispparams->cArgs > 1)
return(DISP_E_PARAMNOTFOUND);
if (!(padVariant = GetAdVariant(pstuff)))
return E_OUTOFMEMORY;
// Get the string argument and make sure it won't overflow our buffers
szwFrame = V_BSTR(pdispparams->rgvarg);
cch = SysStringLen(szwFrame);
if (cch >= MAX_URL_LENGTH-9) // 9 == strlen("target=''")
cch = MAX_URL_LENGTH-10;
::WideCharToMultiByte(CP_ACP, 0, szwFrame, cch, szBuf, cch, NULL, NULL);
szBuf[cch] = '\0';
wsprintf(padVariant->szTarget, "target=\"%s\"", szBuf);
pstuff->lUser = (LONG)padVariant;
return NOERROR;
}
HRESULT
SSOAbout(WORD wInvokeFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult, SSSTUFF *pstuff)
{
char sz[MAX_PATH];
CHAR szAdRotAboutFormat[DEFAULTSTRSIZE];
OLECHAR wsz[MAX_PATH];
#ifdef _DEBUG
char *szVersion = "Debug";
#else
char *szVersion = "Release";
#endif
CchLoadStringOfId(SSO_ADROT_ABOUT_FORMAT, szAdRotAboutFormat, DEFAULTSTRSIZE);
wsprintf(sz, szAdRotAboutFormat, szVersion, __DATE__, __TIME__);
::MultiByteToWideChar(CP_ACP, 0, sz, -1, wsz, sizeof(wsz) / sizeof(OLECHAR));
::VariantInit(pvarResult);
pvarResult->vt = VT_BSTR;
V_BSTR(pvarResult) = ::SysAllocString(wsz);
return(NOERROR);
}
/*--------------------------------------------------------------------------+
| DllMain |
+--------------------------------------------------------------------------*/
BOOL WINAPI
DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved)
{
if (!SSODllMain(hInstance, ulReason, pvReserved))
return(FALSE);
if (DLL_PROCESS_ATTACH == ulReason)
{
#ifdef _DEBUG
srand(1);
#else
srand(::GetTickCount());
#endif // _DEBUG
if (!(g_hAdHeap = ::HeapCreate(0, 8192, 0)))
return FALSE;
}
else if (DLL_PROCESS_DETACH == ulReason)
{
if (g_hAdHeap)
::HeapDestroy(g_hAdHeap);
g_hAdHeap = NULL;
}
return(TRUE);
}