//=======================================================================
//
//  Copyright (c) 2001 Microsoft Corporation.  All Rights Reserved.
//
//  File:	URLLogging.cpp
//
//  Description:
//
//		URL logging utility class
//		This class helps you construct the server ping URL and
//		then send the ping to the designed server.
//
//		The default base URL is defined in IUIdent, under section [IUPingServer]
//		and entry is "ServerUrl".
//
//		This class implements single-thread version only. So it is suitable
//		to call it at operation level, i.e., create a separate object
//		for each operation in a single thread.
//
//		The ping string send to the ping server has the following format:
//			/wutrack.bin
//			?U=<user>
//			&C=<client>
//			&A=<activity>
//			&I=<item>
//			&D=<device>
//			&P=<platform>
//			&L=<language>
//			&S=<status>
//			&E=<error>
//			&M=<message>
//			&X=<proxy>
//		where
//			<user>		a static 128-bit value that unique-ifies each copy
//						of Windows installed.  The class will automatically
//						reuse one previously assigned to the running OS; or
//						will generate one if it does not exist.
//			<client>	a string that identifies the entity that performed
//						activity <activity>.  Here are the possible values
//						and their meanings:
//							"iu"			IU control
//							"au"			Automatic Updates
//							"du"			Dynamic Update
//							"CDM"			Code Download Manager
//							"IU_SITE"		IU Consumer site
//							"IU_Corp"		IU Catalog site
//			<activity>	a letter that identifies the activity performed.
//						Here are the possible values and their meanings:
//							"n"				IU control initization
//							"d"				detection
//							"s"				self-update
//							"w"				download
//							"i"				installation
//			<item>		a string that identifies an update item.
//			<device>	a string that identifies either a device's PNPID when
//						device driver not found during detection; or a
//						PNPID/CompatID used by item <item> for activity
//						<activity> if the item is a device driver.
//			<platform>	a string that identifies the platform of the running
//						OS and processor architecture.  The class will
//						compute this value for the pingback.
//			<language>	a string that identifies the language of the OS
//						binaries.  The class will compute this value for the
//						pingback.
//			<status>	a letter that specifies the status that activity
//						<activity> reached.  Here are the possible values and
//						 their meanings:
//							"s"				succeeded
//							"r"				succeeded (reboot required)
//							"f"				failed
//							"c"				cancelled by user
//							"d"				declined by user
//							"n"				no items
//							"p"				pending
//			<error>		a 32-bit error code in hex (w/o "0x" as prefix).
//			<message>	a string that provides additional information for the
//						status <status>.
//			<proxy>		a 32-bit random value in hex for overriding proxy
//						caching.  This class will compute this value for
//						each pingback.
//
//=======================================================================

#include <tchar.h>
#include <windows.h>		// ZeroMemory()
#include <shlwapi.h>		// PathAppend()
#include <stdlib.h>			// srand(), rand(), malloc() and free()
#include <sys/timeb.h>		// _ftime() and _timeb
#include <malloc.h>			// malloc() and free()

#include <fileutil.h>		// GetIndustryUpdateDirectory()
#include <logging.h>		// LOG_Block, LOG_ErrorMsg, LOG_Error and LOG_Internet
#include <MemUtil.h>		// USES_IU_CONVERSION, W2T() and T2W()
#include <osdet.h>			// LookupLocaleString()
#include <download.h>		// DownloadFile()
#include <wusafefn.h>		// PathCchAppend()
#include <safefunc.h>		// SafeFreeNULL()
#include <MISTSafe.h>

#include <URLLogging.h>

// Header of the log file
typedef struct tagULHEADER
{
	WORD wVersion;		// file version
} ULHEADER, PULHEADER;

#define ARRAYSIZE(x)	(sizeof(x)/sizeof(x[0]))

#define CACHE_FILE_VERSION	((WORD) 10004)	// must be bigger what we had in V3 (10001)

// bug 600602: must end all server URL with '/'
const TCHAR c_tszLiveServerUrl[] = _T("http://wustat.windows.com/");


HRESULT ValidateFileHeader(HANDLE hFile, BOOL fCheckHeader, BOOL fFixHeader);

#ifdef DBG
BOOL MustPingOffline(void)
{
	BOOL fRet = FALSE;
	HKEY hkey;

	if (NO_ERROR == RegOpenKeyEx(
						HKEY_LOCAL_MACHINE,
						_T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate"),
						0,
						KEY_QUERY_VALUE | KEY_SET_VALUE,
						&hkey))
	{
		DWORD	dwForceOfflinePing;
		DWORD	dwSize = sizeof(dwForceOfflinePing);
		DWORD	dwType;

		if (NO_ERROR == RegQueryValueEx(
							hkey,
							_T("ForceOfflinePing"),
							0,
							&dwType,
							(LPBYTE) &dwForceOfflinePing,
							&dwSize))
		{
			if (REG_DWORD == dwType &&
				sizeof(dwForceOfflinePing) == dwSize &&
				1 == dwForceOfflinePing)
			{
				fRet = TRUE;
			}
		}
		RegCloseKey(hkey);
	}
	return fRet;
}
#endif

// ----------------------------------------------------------------------------------
//
// PUBLIC MEMBER FUNCTIONS
//
// ----------------------------------------------------------------------------------

CUrlLog::CUrlLog(void)
: m_ptszLiveServerUrl(NULL), m_ptszCorpServerUrl(NULL)
{
	Init();
	m_tszDefaultClientName[0] = _T('\0');
}


CUrlLog::CUrlLog(LPCTSTR ptszClientName, LPCTSTR ptszLiveServerUrl, LPCTSTR ptszCorpServerUrl)
: m_ptszLiveServerUrl(NULL), m_ptszCorpServerUrl(NULL)
{
	Init();
	(void) SetDefaultClientName(ptszClientName);
	(void) SetLiveServerUrl(ptszLiveServerUrl);
	(void) SetCorpServerUrl(ptszCorpServerUrl);
}


CUrlLog::~CUrlLog(void)
{
	if (NULL != m_ptszLiveServerUrl)
	{
		free(m_ptszLiveServerUrl);
	}
	if (NULL != m_ptszCorpServerUrl)
	{
		free(m_ptszCorpServerUrl);
	}
}

// Assume ptszServerUrl, if non-NULL, is of size INTERNET_MAX_URL_LENGTH in TCHARs
BOOL CUrlLog::SetServerUrl(LPCTSTR ptszUrl, LPTSTR & ptszServerUrl)
{
	LPTSTR ptszEnd = NULL;
	size_t cchRemaining = 0;

	if (NULL == ptszUrl ||
		_T('\0') == *ptszUrl)
	{
		SafeFreeNULL(ptszServerUrl);
	}
	else if (
		// Ensure ptszServerUrl is malloc'ed
		(NULL == ptszServerUrl &&
		 NULL == (ptszServerUrl = (LPTSTR) malloc(sizeof(TCHAR) * INTERNET_MAX_URL_LENGTH))) ||
		// Copy URL
		FAILED(StringCchCopyEx(ptszServerUrl, INTERNET_MAX_URL_LENGTH, ptszUrl, &ptszEnd, &cchRemaining, MISTSAFE_STRING_FLAGS)) ||
		// Ensure URL ending with '/'
		(_T('/') != ptszEnd[-1] &&
		 FAILED(StringCchCopyEx(ptszEnd, cchRemaining, _T("/"), NULL, NULL, MISTSAFE_STRING_FLAGS))))
	{
		SafeFreeNULL(ptszServerUrl);
		return FALSE;
	}
	return TRUE;
}


// Watch out for the size of m_tszDefaultClientName.
BOOL CUrlLog::SetDefaultClientName(LPCTSTR ptszClientName)
{
	if (NULL == ptszClientName)
	{
		// E_INVALIDARG
		m_tszDefaultClientName[0] = _T('\0');
		return FALSE;
	}

	return SUCCEEDED(StringCchCopyEx(m_tszDefaultClientName, ARRAYSIZE(m_tszDefaultClientName), ptszClientName, NULL, NULL, MISTSAFE_STRING_FLAGS));
}


HRESULT CUrlLog::Ping(
				BOOL fOnline,			// online or offline ping
				URLLOGDESTINATION destination,	// live or corp WU ping server
				PHANDLE phQuitEvents,	// ptr to handles for cancelling the operation
				UINT nQuitEventCount,	// number of handles
				URLLOGACTIVITY activity,// activity code
				URLLOGSTATUS status,	// status code
				DWORD dwError,			// error code
				LPCTSTR ptszItemID,		// uniquely identify an item
				LPCTSTR ptszDeviceID,	// PNPID or CompatID
				LPCTSTR ptszMessage,	// additional info
				LPCTSTR ptszClientName)	// client name string
{
	LOG_Block("CUrlLog::Ping");

	LPTSTR	ptszUrl = NULL;
	HRESULT hr = E_FAIL;

	switch (activity)
	{
	case URLLOGACTIVITY_Initialization:	// fall thru
	case URLLOGACTIVITY_Detection:		// fall thru
	case URLLOGACTIVITY_SelfUpdate:		// fall thru
	case URLLOGACTIVITY_Download:		// fall thru
	case URLLOGACTIVITY_Installation:
		break;
	default:
		hr = E_INVALIDARG;
		goto CleanUp;
	}

	switch (status)
	{
	case URLLOGSTATUS_Success:		// fall thru
	case URLLOGSTATUS_Reboot:		// fall thru
	case URLLOGSTATUS_Failed:		// fall thru
	case URLLOGSTATUS_Cancelled:	// fall thru
	case URLLOGSTATUS_Declined:		// fall thru
	case URLLOGSTATUS_NoItems:		// fall thru
	case URLLOGSTATUS_Pending:
		break;
	default:
		hr = E_INVALIDARG;
		goto CleanUp;
	}

	//
	// handle optional (nullable) arguments
	//
	if (NULL == ptszClientName)
	{
		ptszClientName = m_tszDefaultClientName;
	}

	if (_T('\0') == *ptszClientName)
	{
		LOG_Error(_T("client name not initialized"));
		hr = E_INVALIDARG;
		goto CleanUp;
	}

	switch (destination)
	{
	case URLLOGDESTINATION_DEFAULT:
		destination = (
			NULL == m_ptszCorpServerUrl ||
			_T('\0') == *m_ptszCorpServerUrl) ?
			URLLOGDESTINATION_LIVE :
			URLLOGDESTINATION_CORPWU;
		break;
	case URLLOGDESTINATION_LIVE:	// fall thru
	case URLLOGDESTINATION_CORPWU:
		break;
	default:
		hr = E_INVALIDARG;
		goto CleanUp;
	}

	LPCTSTR ptszServerUrl;

	if (URLLOGDESTINATION_LIVE == destination)
	{
		if (NULL != m_ptszLiveServerUrl)
		{
			ptszServerUrl = m_ptszLiveServerUrl;
		}
		else
		{
			ptszServerUrl = c_tszLiveServerUrl;
		}
	}
	else
	{
		ptszServerUrl = m_ptszCorpServerUrl;
	}

	if (NULL == ptszServerUrl ||
		_T('\0') == *ptszServerUrl)
	{
		LOG_Error(_T("status server Url not initialized"));
		hr = E_INVALIDARG;
		goto CleanUp;
	}

	if (NULL == (ptszUrl = (TCHAR*) malloc(sizeof(TCHAR) * INTERNET_MAX_URL_LENGTH)))
	{
		hr = E_OUTOFMEMORY;
		goto CleanUp;
	}

	if (FAILED(hr = MakePingUrl(
						ptszUrl,
						INTERNET_MAX_URL_LENGTH,
						ptszServerUrl,
						ptszClientName,
						activity,
						ptszItemID,
						ptszDeviceID,
						status,
						dwError,
						ptszMessage)))
	{
		goto CleanUp;
	}

	if (fOnline)
	{
		hr = PingStatus(destination, ptszUrl, phQuitEvents, nQuitEventCount);
		if (SUCCEEDED(hr))
		{
			(void) Flush(phQuitEvents, nQuitEventCount);
			goto CleanUp;
		}
	}

	{
		USES_IU_CONVERSION;

		LPWSTR pwszUrl = T2W(ptszUrl);
		HRESULT hr2;

		if (NULL == pwszUrl)
		{
			hr = E_OUTOFMEMORY;
			goto CleanUp;
		}

		ULENTRYHEADER ulentryheader;
		ulentryheader.progress = URLLOGPROGRESS_ToBeSent;
		ulentryheader.destination = destination;
		ulentryheader.wRequestSize = lstrlen(ptszUrl) + 1;
		ulentryheader.wServerUrlLen = (WORD) lstrlen(ptszServerUrl);

		if (SUCCEEDED(hr2 = SaveEntry(ulentryheader, pwszUrl)))
		{
			hr = S_FALSE;
		}
		else if (SUCCEEDED(hr))
		{
			hr = hr2;
		}
	}

CleanUp:
	if (NULL != ptszUrl)
	{
		free(ptszUrl);
	}

	return hr;
}


// ----------------------------------------------------------------------------------
//
// PRIVATE MEMBER FUNCTIONS
//
// ----------------------------------------------------------------------------------

// Init member variables within a constructor.  No memory clean-up done here.
void CUrlLog::Init()
{
	LookupPingID();
	LookupPlatform();
	LookupSystemLanguage();
	GetLogFileName();
}


// ----------------------------------------------------------------------------------
//	Construct a URL used to ping server
//
//	Returned value indicates success/failure
// ----------------------------------------------------------------------------------
HRESULT CUrlLog::MakePingUrl(
			LPTSTR	ptszUrl,			// buffer to receive result
			int		cChars,				// the number of chars this buffer can take, including ending null
			LPCTSTR ptszBaseUrl,		// server URL
			LPCTSTR ptszClientName,		// which client called
			URLLOGACTIVITY activity,
			LPCTSTR ptszItemID,
			LPCTSTR ptszDeviceID,
			URLLOGSTATUS status,
			DWORD	dwError,			// return code of activity
			LPCTSTR	ptszMessage)
{
	HRESULT hr = E_FAIL;
	LPTSTR ptszEscapedItemID = NULL;
	LPTSTR ptszEscapedDeviceID = NULL;
	LPTSTR ptszEscapedMessage = NULL;

	LOG_Block("CUrlLog::MakePingUrl");

	// Retry to get info strings if we failed within the constructor.
	if (_T('\0') == m_tszPlatform[0] ||
		_T('\0') == m_tszLanguage[0])
	{
		LOG_Error(_T("Invalid platform or language info string"));
		hr = E_UNEXPECTED;
		goto CleanUp;
	}

	// allocate enough memory for URL manipulation. Since the buffer needs
	// to be at least 2Kbytes in size, stack buffer is not suitable here.
	// we involve mem utility to similate stack memory allocation
	if ((NULL != ptszItemID &&
		 (NULL == (ptszEscapedItemID = (LPTSTR) malloc(sizeof(TCHAR) * INTERNET_MAX_URL_LENGTH)) ||
		  !EscapeString(ptszItemID, ptszEscapedItemID, INTERNET_MAX_URL_LENGTH))) ||
		(NULL != ptszDeviceID &&
		 (NULL == (ptszEscapedDeviceID = (LPTSTR) malloc(sizeof(TCHAR) * INTERNET_MAX_URL_LENGTH)) ||
		  !EscapeString(ptszDeviceID, ptszEscapedDeviceID, INTERNET_MAX_URL_LENGTH))) ||
		(NULL != ptszMessage &&
		 (NULL == (ptszEscapedMessage = (LPTSTR) malloc(sizeof(TCHAR) * INTERNET_MAX_URL_LENGTH)) ||
		  !EscapeString(ptszMessage, ptszEscapedMessage, INTERNET_MAX_URL_LENGTH))))
	{
		// Either out-of-memory or the escaped string is too lengthy.
		LOG_Error(_T("Out of memory or EscapeString failure"));
		hr = E_OUTOFMEMORY;	// actually could be HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) as well
		goto CleanUp;
	}

	const TCHAR c_tszEmpty[] = _T("");

	// Use system time as proxy cache breaker
	SYSTEMTIME st;

	GetSystemTime(&st);

	hr = StringCchPrintfEx(
				ptszUrl,
				cChars,
				NULL,
				NULL,
				MISTSAFE_STRING_FLAGS,
				_T("%swutrack.bin?U=%s&C=%s&A=%c&I=%s&D=%s&P=%s&L=%s&S=%c&E=%08x&M=%s&X=%02d%02d%02d%02d%02d%02d%03d"),
				NULL == ptszBaseUrl ? c_tszEmpty : ptszBaseUrl,					// server URL
				m_tszPingID,														// ping ID
				ptszClientName,													// client name
				activity,														// activity code
				NULL == ptszEscapedItemID ? c_tszEmpty : ptszEscapedItemID,		// escaped item ID
				NULL == ptszEscapedDeviceID ? c_tszEmpty : ptszEscapedDeviceID,	// escaped device ID
				m_tszPlatform,													// platform info
				m_tszLanguage,													// sys lang info
				status,															// status code
				dwError,														// activity error code
				NULL == ptszEscapedMessage ? c_tszEmpty : ptszEscapedMessage,	// escaped message str
				st.wYear % 100,													// proxy override
				st.wMonth,
				st.wDay,
				st.wHour,
				st.wMinute,
				st.wSecond,
				st.wMilliseconds);

CleanUp:
	if (NULL != ptszEscapedItemID)
	{
		free(ptszEscapedItemID);
	}
	if (NULL != ptszEscapedDeviceID)
	{
		free(ptszEscapedDeviceID);
	}
	if (NULL != ptszEscapedMessage)
	{
		free(ptszEscapedMessage);
	}

	return hr;
}



// Obtain the existing ping ID from the registry, or generate one if not available.
void CUrlLog::LookupPingID(void)
{
	const TCHAR c_tszRegKeyWU[] = _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate");
	const TCHAR c_tszRegUrlLogPingID[] = _T("PingID");

	BOOL	fRet = FALSE;
	LONG	lErr;
	HKEY	hkey;
	UUID	uuidPingID;

	LOG_Block("CUrlLog::LookupPingID");

	//fixcode: wrap registry manipulation w/ a critical section

	if (NO_ERROR == (lErr = RegOpenKeyEx(
								HKEY_LOCAL_MACHINE,
								c_tszRegKeyWU,
								0,
								KEY_QUERY_VALUE | KEY_SET_VALUE,
								&hkey)))
	{
		BOOL	fFixValue = TRUE;
		DWORD	dwSize = sizeof(uuidPingID);
		DWORD	dwType;

		lErr = RegQueryValueEx(
					hkey,
					c_tszRegUrlLogPingID,
					0,
					&dwType,
					(LPBYTE) &uuidPingID,
					&dwSize);
		if (NO_ERROR == lErr)
		{
			if (REG_BINARY == dwType && sizeof(uuidPingID) == dwSize)
			{
				// successfully read ping ID from registry
				fFixValue = FALSE;
				fRet = TRUE;
			}
		}
		else if (ERROR_MORE_DATA == lErr || ERROR_FILE_NOT_FOUND == lErr)
		{
			// Data not of right length or not found.
			// We will try to fix it.  Treat it as no error for now.
			lErr = NO_ERROR;
		}

		if (NO_ERROR == lErr)
		{
			if (fFixValue)
			{
				MakeUUID(&uuidPingID);
				lErr = RegSetValueEx(
							hkey,
							c_tszRegUrlLogPingID,
							0,
							REG_BINARY,
							(CONST BYTE*) &uuidPingID,
							sizeof(uuidPingID));
				if (NO_ERROR == lErr)
				{
					fRet = TRUE;	// This is not a final value yet. Still depends on RegCloseKey().
				}
#ifdef DBG
				else
				{
					LOG_ErrorMsg(lErr);
				}
#endif
			}
		}
#ifdef DBG
		else
		{
			LOG_ErrorMsg(lErr);
		}
#endif

		if (NO_ERROR != (lErr = RegCloseKey(hkey)))
		{
			if (fFixValue)
			{
				fRet = FALSE;
			}
			LOG_ErrorMsg(lErr);
		}
	}
#ifdef DBG
	else
	{
		LOG_ErrorMsg(lErr);
	}
#endif

	if (!fRet)
	{
		// Only happens if something failed.
		// Make a ping ID of zeroes.
		ZeroMemory(&uuidPingID, sizeof(uuidPingID));
	}

	LPTSTR p = m_tszPingID;
	LPBYTE q = (LPBYTE) &uuidPingID;
	for (int i = 0; i<sizeof(uuidPingID); i++, q++)
	{
		BYTE nibble = *q >> 4;	// high nibble
		*p++ = nibble >= 0xA ? _T('a') + (nibble - 0xA) : _T('0') + nibble;
		nibble = *q & 0xF;	// low nibble
		*p++ = nibble >= 0xA ? _T('a') + (nibble - 0xA) : _T('0') + nibble;
	}
	*p = _T('\0');
}



// Obtain platfrom info for ping
void CUrlLog::LookupPlatform(void)
{
	LOG_Block("CUrlLog::LookupPlatform");

	m_tszPlatform[0] = _T('\0');

	OSVERSIONINFOEX osversioninfoex;

	ZeroMemory(&osversioninfoex, sizeof(osversioninfoex));

	// pretend to be OSVERSIONINFO for W9X/Mil
	osversioninfoex.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

	if (!GetVersionEx((LPOSVERSIONINFO) &osversioninfoex))
	{
		LOG_ErrorMsg(GetLastError());
		return;
	}

	if (VER_PLATFORM_WIN32_NT == osversioninfoex.dwPlatformId &&
		(5 <= osversioninfoex.dwMajorVersion ||
		 (4 == osversioninfoex.dwMajorVersion &&
		  6 <= osversioninfoex.wServicePackMajor)))
	{
		// OS is Windows NT/2000 or later: Windows NT 4.0 SP6 or later.
		// It supports OSVERSIONINFOEX.
		osversioninfoex.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);	// use actual size

		if (!GetVersionEx((LPOSVERSIONINFO) &osversioninfoex))
		{
			LOG_ErrorMsg(GetLastError());
			return;
		}
	}

	SYSTEM_INFO systeminfo;

	GetSystemInfo(&systeminfo);

	(void) StringCchPrintfEx(
						m_tszPlatform,
						ARRAYSIZE(m_tszPlatform),
						NULL,
						NULL,
						MISTSAFE_STRING_FLAGS,
						_T("%lx.%lx.%lx.%lx.%x.%x.%x"),
						osversioninfoex.dwMajorVersion,
						osversioninfoex.dwMinorVersion,
						osversioninfoex.dwBuildNumber,
						osversioninfoex.dwPlatformId,
						osversioninfoex.wSuiteMask,
						osversioninfoex.wProductType,
						systeminfo.wProcessorArchitecture);
}



// Obtain system language info for ping
void CUrlLog::LookupSystemLanguage(void)
{
	LOG_Block("CUrlLog::LookupSystemLanguage");

	(void) LookupLocaleString(m_tszLanguage, ARRAYSIZE(m_tszLanguage), FALSE);

	if (0 == _tcscmp(m_tszLanguage, _T("Error")))
	{
		LOG_Error(_T("call to LookupLocaleString() failed."));
		m_tszLanguage[0] = _T('\0');
	}
}

	
// Ping server to report status
//		ptszUrl - the URL string to be pinged
//		phQuitEvents - ptr to handles for cancelling the operation
//		nQuitEventCount - number of handles
HRESULT CUrlLog::PingStatus(URLLOGDESTINATION destination, LPCTSTR ptszUrl, PHANDLE phQuitEvents, UINT nQuitEventCount) const
{
#ifdef DBG
	LOG_Block("CUrlLog::PingStatus");

	LOG_Internet(_T("Ping request=\"%s\""), ptszUrl);

	if (MustPingOffline())
	{
		LOG_Internet(_T("ForceOfflinePing = 1"));
		return HRESULT_FROM_WIN32(ERROR_CONNECTION_INVALID);
	}
#endif

	if (!IsConnected(ptszUrl, URLLOGDESTINATION_LIVE == destination))
	{
		// There is no connection.
		LOG_ErrorMsg(ERROR_CONNECTION_INVALID);
		return HRESULT_FROM_WIN32(ERROR_CONNECTION_INVALID);
	}

	if (!HandleEvents(phQuitEvents, nQuitEventCount))
	{
		LOG_ErrorMsg(E_ABORT);
		return E_ABORT;
	}

	TCHAR tszIUdir[MAX_PATH];

	GetIndustryUpdateDirectory(tszIUdir);

	DWORD dwFlags = WUDF_CHECKREQSTATUSONLY;	// we don't actually need a file,
												//  just need to check return code
	if (URLLOGDESTINATION_CORPWU == destination)
	{
		// don't allow proxy if destination is corp WU
		dwFlags |= WUDF_DONTALLOWPROXY;
	}

	HRESULT hr = DownloadFile(
					ptszUrl, 
					tszIUdir,	// local directory to download file to
					NULL,		// optional local file name for downloaded file
								// if pszLocalPath doesn't contain a file name
					NULL,		// ptr to bytes downloaded for this file
					phQuitEvents,	// quit event, if signalled, abort downloading
					nQuitEventCount,
					NULL,
					NULL,		// parameter for call back function to use
					dwFlags
					);
#ifdef DBG
	if (FAILED(hr))
	{
		LOG_Error(_T("DownloadFile() returned error %lx"), hr);
	}
#endif

	return hr;
}



// Obtain file names for offline ping
void CUrlLog::GetLogFileName(void)
{
	const TCHAR c_tszLogFile_Local[] = _T("urllog.dat");

	GetIndustryUpdateDirectory(m_tszLogFile);

	if (FAILED(PathCchAppend(m_tszLogFile, ARRAYSIZE(m_tszLogFile), c_tszLogFile_Local)))
	{
		m_tszLogFile[0] = _T('\0');
	}
}


// Read cache entry header and request in entry
//		hFile - an open file handle to read the entry from
//		ulentryheader - reference to the struct to store the entry header
//		pwszBuffer - the WCHAR buffer to store the request (including trailing null character) in the entry
//		dwBufferSize - the size of buffer in WCHARs
// Returned value:
//		S_OK - entry successfully read
//		S_FALSE - no more entry to read from the file
//		other - error codes
HRESULT CUrlLog::ReadEntry(HANDLE hFile, ULENTRYHEADER & ulentryheader, LPWSTR pwszBuffer, DWORD dwBufferSize) const
{
	LOG_Block("CUrlLog::ReadEntry");

	DWORD dwBytes;
	DWORD dwErr;

	if (!ReadFile(
			hFile,
			&ulentryheader,
			sizeof(ulentryheader),
			&dwBytes,
			NULL))
	{
		// We failed to read the entry header.
		// There is nothing we can do at this point.
		dwErr = GetLastError();
		LOG_ErrorMsg(dwErr);
		return HRESULT_FROM_WIN32(dwErr);
	}

	if (0 == dwBytes)
	{
		// This is the end of the file.
		// There is no other entries after this point.
		return S_FALSE;
	}

	if (sizeof(ulentryheader) < dwBytes ||
		(URLLOGPROGRESS_ToBeSent != ulentryheader.progress &&
		 URLLOGPROGRESS_Sent != ulentryheader.progress) ||
		(URLLOGDESTINATION_LIVE != ulentryheader.destination &&
		 URLLOGDESTINATION_CORPWU != ulentryheader.destination) ||
		dwBufferSize < ulentryheader.wRequestSize ||
		ulentryheader.wRequestSize <= ulentryheader.wServerUrlLen)
	{
		LOG_Error(_T("Invalid entry header"));
		return E_FAIL;
	}

	if (!ReadFile(
				hFile,
				pwszBuffer,
				sizeof(WCHAR) * ulentryheader.wRequestSize,
				&dwBytes,
				NULL))
	{
		// We failed to read the string in the entry.
		dwErr = GetLastError();
		LOG_ErrorMsg(dwErr);
		return HRESULT_FROM_WIN32(dwErr);
	}

	if (dwBytes < sizeof(WCHAR) * ulentryheader.wRequestSize ||
		_T('\0') != pwszBuffer[ulentryheader.wRequestSize-1] ||
		ulentryheader.wRequestSize-1 != lstrlenW(pwszBuffer))
	{
		// The entry does not contain the complete string.
		return E_FAIL;
	}

	return S_OK;
}


// Save a string to the log file
//		destination - going to the live or corp WU ping server
//		wServerUrlLen - length of server URL part of the request, in WCHARs (not including trailing NULL)
//		pwszString - the string to be saved into the specific log file
// Returned value:
//		S_OK - entry was written to file
//		S_FALSE - the file was created by a CUrlLog class of newer version than this; entry was not written to file
//		other - error codes; entry was not written to file
HRESULT CUrlLog::SaveEntry(ULENTRYHEADER & ulentryheader, LPCWSTR pwszString) const
{
	HRESULT		hr;
	BOOL		fDeleteFile = FALSE;
	HANDLE		hFile = INVALID_HANDLE_VALUE;
	DWORD		dwBytes;

	LOG_Block("CUrlLog::SaveEntry");

	LOG_Internet(
			_T("destination = %s"),
			URLLOGDESTINATION_LIVE == ulentryheader.destination ? _T("live") : _T("corp WU"));

	if (_T('\0') == m_tszLogFile[0])
	{
		hr = E_UNEXPECTED;
		LOG_Error(_T("log file name not initialized"));
		goto CleanUp;
	}

	if(INVALID_HANDLE_VALUE == (hFile = CreateFile(
							m_tszLogFile,
							GENERIC_READ | GENERIC_WRITE,
							0,						// no sharing
							NULL,
							OPEN_ALWAYS,
							FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_RANDOM_ACCESS,
							NULL)))
	{
		// We failed to open or create the file.
		// Someone may be currently using it.

		//fixcode: allow multiple pingback users
		// access the file sequentially.
		hr = HRESULT_FROM_WIN32(GetLastError());
		LOG_ErrorMsg(hr);
		goto CleanUp;
	}


	hr = ValidateFileHeader(hFile, ERROR_ALREADY_EXISTS == GetLastError(), TRUE);
	if (S_OK != hr)
	{
		if (S_FALSE != hr)
		{
			// The file header is bad or there was problem validating it.
			fDeleteFile = TRUE;		// destroy the file and fail the function
		}
		// else
			// The file header has a newer version than this library code.
			// Keep the file around.

		goto CleanUp;
	}


	// Set outselves to the right position before writing to the file.
	DWORD nCurrPos;

	if (INVALID_SET_FILE_POINTER == (nCurrPos = SetFilePointer(
										hFile,
										0,
										NULL,
										FILE_END)))
	{
		hr = HRESULT_FROM_WIN32(GetLastError());
		LOG_ErrorMsg(hr);
		goto CleanUp;
	}

	// Write the entry to the log.
	if (!WriteFile(
			hFile,
			&ulentryheader,
			sizeof(ulentryheader),
			&dwBytes,
			NULL))
	{
		hr = HRESULT_FROM_WIN32(GetLastError());
		LOG_ErrorMsg(hr);
	}

	if (SUCCEEDED(hr) &&
		sizeof(ulentryheader) != dwBytes)
	{
		LOG_Error(_T("Failed to write entry header to file (%d bytes VS %d bytes)"), sizeof(ulentryheader), dwBytes);
		hr = E_FAIL;
	}

	if (SUCCEEDED(hr) &&
		!WriteFile(
			hFile,
			pwszString,
			sizeof(WCHAR) * ulentryheader.wRequestSize,
			&dwBytes,
			NULL))
	{
		hr = HRESULT_FROM_WIN32(GetLastError());
		LOG_ErrorMsg(hr);
	}

	if (SUCCEEDED(hr) &&
		sizeof(WCHAR) * ulentryheader.wRequestSize != dwBytes)
	{
		LOG_Error(_T("Failed to write entry header to file (%d bytes VS %d bytes)"), sizeof(WCHAR) * ulentryheader.wRequestSize, dwBytes);
		hr = E_FAIL;
	}

	// We failed to wrote the entry into the log.
	if (FAILED(hr))
	{
		// We don't want to get rid of the other entries.
		// We can only try to remove the portion of the entry
		// we have appended from the file.
		if (INVALID_SET_FILE_POINTER == SetFilePointer(
											hFile,
											nCurrPos,
											NULL,
											FILE_BEGIN) ||
			!SetEndOfFile(hFile))
		{
			// We failed to remove the new entry.
			hr = HRESULT_FROM_WIN32(GetLastError());
			LOG_ErrorMsg(hr);
			fDeleteFile = TRUE;
		}
		// else
			// We successfully got rid of this entry.
			// And preserved existing entries in log.
	}

CleanUp:
	if (INVALID_HANDLE_VALUE != hFile)
	{
		CloseHandle(hFile);
	}
	if (fDeleteFile)
	{
		(void) DeleteFile(m_tszLogFile);
		// We don't delete the log file if the operation was successful.
		// Thus, no need to modify the fRet value even if DeleteFile() failed.
	}

	return hr;
}



// Send all pending (offline) ping requests to server
HRESULT CUrlLog::Flush(PHANDLE phQuitEvents, UINT nQuitEventCount)
{
	LPWSTR	pwszBuffer = NULL;
	LPTSTR	ptszUrl = NULL;
	HANDLE	hFile = INVALID_HANDLE_VALUE;
	BOOL	fKeepFile = FALSE;
	DWORD	dwErr;
	HRESULT	hr;

	LOG_Block("CUrlLog::Flush");

	if (NULL == (pwszBuffer = (LPWSTR) malloc(sizeof(WCHAR) * INTERNET_MAX_URL_LENGTH)) ||
		NULL == (ptszUrl = (LPTSTR) malloc(sizeof(TCHAR) * INTERNET_MAX_URL_LENGTH)))
	{
		hr = E_OUTOFMEMORY;
		goto CleanUp;
	}

	if (_T('\0') == m_tszLogFile[0])
	{
		hr = E_UNEXPECTED;
		LOG_Error(_T("log file name not initialized"));
		goto CleanUp;
	}

	// Open existing log
	if(INVALID_HANDLE_VALUE == (hFile = CreateFile(
							m_tszLogFile,
							GENERIC_READ | GENERIC_WRITE,
							0,						// no sharing
							NULL,
							OPEN_EXISTING,
							FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_RANDOM_ACCESS,
							NULL)))
	{
		// We failed to open the file.
		// The file may not exist or someone may be currently using it.
		dwErr = GetLastError();

		if (ERROR_FILE_NOT_FOUND == dwErr)
		{
			// We are done.  There is nothing more to do.
			hr = S_OK;
		}
		else
		{
			//fixcode: allow multiple pingback users
			// access the file sequentially.
			LOG_ErrorMsg(dwErr);
			hr = HRESULT_FROM_WIN32(dwErr);
		}
		goto CleanUp;
	}


	// File opened.  Check header.
	hr = ValidateFileHeader(hFile, TRUE, FALSE);

	if (S_OK != hr)
	{
		if (S_FALSE == hr)
		{
			// The file header has a newer version than this library code.
			goto CleanUp; // Keep the file around.
		}
		// else
			// The file header is bad or there was problem validating it.
			// destroy the file and fail the function
	}
	else
	{
		BOOL fLiveServerFailed = FALSE;
		BOOL fCorpServerFailed = FALSE;

		// It is time to read an entry.
		for (;;)
		{
			ULENTRYHEADER ulentryheader;

			if (!HandleEvents(phQuitEvents, nQuitEventCount))
			{
				hr = E_ABORT;
				LOG_ErrorMsg(hr);
				break;
			}

			// Assume we are in the right position to read
			// the next entry from the file.

			// Read the entry header and request in entry.
			if (FAILED(hr = ReadEntry(hFile, ulentryheader, pwszBuffer, INTERNET_MAX_URL_LENGTH)))
			{
				LOG_Error(_T("Failed to read entry from cache (%#lx)"), hr);
				break;
			}

			if (S_FALSE == hr)
			{
				// There are no more unprocessed entries.
				hr = S_OK;
				break;
			}

			// We have successfully read the entry from the cache file.
			if (URLLOGPROGRESS_Sent != ulentryheader.progress)
			{
				// The entry hasn't been successfully sent yet.
				LPCTSTR	ptszBaseUrl = NULL;
				BOOL *pfWhichServerFailed;

				if (URLLOGDESTINATION_LIVE == ulentryheader.destination)
				{
					ptszBaseUrl = m_ptszLiveServerUrl;
					pfWhichServerFailed = &fLiveServerFailed;
				}
				else
				{
					ptszBaseUrl = m_ptszCorpServerUrl;
					pfWhichServerFailed = &fCorpServerFailed;
				}

				if (*pfWhichServerFailed)
				{
					continue;	// this base URL has failed before.  go on to the next entry.
				}

				LPTSTR ptszRelativeUrl;

				USES_IU_CONVERSION;

				if (NULL == (ptszRelativeUrl = W2T(pwszBuffer + ulentryheader.wServerUrlLen)))
				{
					// Running out of memory.  Will retry later.
					hr = E_OUTOFMEMORY;
					break;
				}

				if (NULL != ptszBaseUrl)
				{
					// Form the request URL
					DWORD dwUrlLen = INTERNET_MAX_URL_LENGTH;

					if (S_OK != UrlCombine(	// requires IE3 for 95/NT4
										ptszBaseUrl,
										ptszRelativeUrl,
										ptszUrl,
										&dwUrlLen,
										URL_DONT_SIMPLIFY))
					{
						// Either the buffer is too small to hold both the base and
						// the relative URLs, or the host name is invalid.
						// We will retry this entry just in case we will have a
						// shorter/better host name.
						fKeepFile = TRUE;
						continue;	// go on to the next entry
					}
				}
				else
				{
#if defined(UNICODE) || defined(_UNICODE)
					if (FAILED(hr = StringCchCopyExW(ptszUrl, INTERNET_MAX_URL_LENGTH, pwszBuffer, NULL, NULL, MISTSAFE_STRING_FLAGS)))
					{
						LOG_Error(_T("Failed to construct ping URL (%#lx)"), hr);
						break;
					}
#else
					if (0 == AtlW2AHelper(ptszUrl, pwszBuffer, INTERNET_MAX_URL_LENGTH))
					{
						// The buffer is probably too small to hold both the base and
						// the relative URLs.  We will retry this entry just in case
						// we will have a shorter/better host name.
						fKeepFile = TRUE;
						continue;	// go on to the next entry
					}
#endif
				}

				hr = PingStatus(ulentryheader.destination, ptszUrl, phQuitEvents, nQuitEventCount);

				if (FAILED(hr))
				{
					if (E_ABORT == hr)
					{
						break;
					}

					// We will resend this entry later.
					LOG_Internet(_T("Failed to send message (%#lx).  Will retry later."), hr);
					*pfWhichServerFailed = TRUE;
					fKeepFile = TRUE;

					if (fLiveServerFailed && fCorpServerFailed)
					{
						// Failed to send ping messages to both destinations.
						hr = S_OK;
						break;
					}
					continue;
				}

				DWORD	dwBytes;

				// Mark the entry off the cache file.
				ulentryheader.progress = URLLOGPROGRESS_Sent;
				// Go to the beginning of the current entry and change the entry header.
				if (INVALID_SET_FILE_POINTER == SetFilePointer(
													hFile,
													- ((LONG) (sizeof(ulentryheader) +
															   sizeof(WCHAR) * ulentryheader.wRequestSize)),
													NULL,
													FILE_CURRENT) ||
					!WriteFile(
							hFile,
							&ulentryheader,
							sizeof(ulentryheader),
							&dwBytes,
							NULL))
				{
					// We failed to mark this entry 'sent'.
					hr = HRESULT_FROM_WIN32(GetLastError());
					LOG_ErrorMsg(hr);
					break;
				}

				if (sizeof(ulentryheader) != dwBytes)
				{
					// We failed to write the header.
					LOG_Error(_T("Failed to write header (%d bytes VS %d bytes)"), sizeof(ulentryheader), dwBytes);
					hr = E_FAIL;
					break;
				}

				// Set the file pointer to the start of the next entry
				if (INVALID_SET_FILE_POINTER == SetFilePointer(
													hFile,
													sizeof(WCHAR) * ulentryheader.wRequestSize,
													NULL,
													FILE_CURRENT))
				{
					// We failed to skip the current entry.
					hr = HRESULT_FROM_WIN32(GetLastError());
					LOG_ErrorMsg(hr);
					break;
				}
			}
		}
	}

	CloseHandle(hFile);
	hFile = INVALID_HANDLE_VALUE;

	if ((FAILED(hr) && E_ABORT != hr && E_OUTOFMEMORY != hr) ||
		(SUCCEEDED(hr) && !fKeepFile))
	{
		(void) DeleteFile(m_tszLogFile);
	}

CleanUp:
	if (NULL != pwszBuffer)
	{
		free(pwszBuffer);
	}
	if (NULL != ptszUrl)
	{
		free(ptszUrl);
	}
	if (INVALID_HANDLE_VALUE != hFile)
	{
		CloseHandle(hFile);
	}

	return hr;
}



// Escape unsafe chars in a TCHAR string
// Returned value: non-zero if successful; zero otherwise
BOOL EscapeString(
			LPCTSTR	ptszUnescaped,
			LPTSTR	ptszBuffer,
			DWORD	dwCharsInBuffer)
{
	BOOL fRet = FALSE;

	LOG_Block("CUrlLog::EscapeString");

	if (NULL != ptszUnescaped &&
		NULL != ptszBuffer &&
		0 != dwCharsInBuffer)
	{
		for (DWORD i=0, j=0; _T('\0') != ptszUnescaped[i] && j+1<dwCharsInBuffer; i++, j++)
		{
			TCHAR tch = ptszUnescaped[i];

			if ((_T('a') <= tch && _T('z') >= tch) ||
				(_T('A') <= tch && _T('Z') >= tch) ||
				(_T('0') <= tch && _T('9') >= tch) ||
				NULL != _tcschr(_T("-_.!~*'()"), tch))
			{
				ptszBuffer[j] = tch;
			}
			else if (j+3 >= dwCharsInBuffer)
			{
				// We don't have enough buffer to hold the escaped string.
				// Bail out.
				break;
			}
			else
			{
				TCHAR nibble = tch >> 4;

				ptszBuffer[j++]	= _T('%');
				ptszBuffer[j++]	= nibble + (nibble >= 0x0a ? _T('A') - 0x0a : _T('0'));
				nibble = tch & 0x0f;
				ptszBuffer[j]	= nibble + (nibble >= 0x0a ? _T('A') - 0x0a : _T('0'));
			}
		}

		if (_T('\0') == ptszUnescaped[i])
		{
			ptszBuffer[j] = _T('\0');
			fRet = TRUE;
		}
#ifdef DBG
		else
		{
			// Couldn't escape the whole string due to insufficient buffer.
			LOG_ErrorMsg(ERROR_INSUFFICIENT_BUFFER);
		}
#endif
	}
#ifdef DBG
	else
	{
		LOG_ErrorMsg(E_INVALIDARG);
	}
#endif

	return fRet;
}



// Create a UUID that is not linked to MAC address of a NIC, if any, on the system.
//		pUuid - ptr to the UUID structure to hold the returning value.
void MakeUUID(UUID* pUuid)
{
	HRESULT			hr;
	OSVERSIONINFO	osverinfo;

	LOG_Block("CUrlLog::MakeUUID");

	// check OS version
	osverinfo.dwOSVersionInfoSize = sizeof(osverinfo);
	if (!(GetVersionEx(&osverinfo)))
	{
		hr = GetLastError();
#ifdef DBG
		if (FAILED(hr))
		{
			LOG_ErrorMsg(hr);	// log this error
		}
#endif
	}
	else if (5 <= osverinfo.dwMajorVersion &&					// Check for Win2k & up
			 VER_PLATFORM_WIN32_NT == osverinfo.dwPlatformId)
	{
		// The OS is Win2K & up.
		// We can safely use CoCreateGuid().
		hr = CoCreateGuid(pUuid);
		if (SUCCEEDED(hr))
		{
			goto Done;
		}

		LOG_ErrorMsg(hr);	// log this error
	}

	// Either the OS is something older than Win2K, or
	// somehow we failed to get a GUID with CoCreateGuid.
	// We still have to do something to resolve the proxy caching problem.
	// Here we construct this psudo GUID by using:
	// -	ticks since last reboot
	// -	the current process ID
	// -	time in seconds since 00:00:00 1/1/1970 UTC
	// -	fraction of a second in milliseconds for the above time.
	// -	a 15-bit unsigned random number
	//
	pUuid->Data1 = GetTickCount();
	*((DWORD*) &pUuid->Data2) = GetCurrentProcessId();

	// Use the first 6 bytes of m_uuidPingID.Data1 to store sys date/time.
	{
		_timeb tm;

		_ftime(&tm);
		*((DWORD*) &pUuid->Data4) = (DWORD) tm.time;
		((WORD*) &pUuid->Data4)[2] = tm.millitm;
	}

	// Use the last 2 bytes of m_uuidPingID.Data1 to store another random number.
	srand(pUuid->Data1);
	((WORD*) &pUuid->Data4)[3] = (WORD) rand();	// rand() returns only positive values.


Done:
	return;
}


// Check and/or fix (if necessary) the header of the log file.
//
// Returned value:
//		S_OK - the header has been fixed or the file contains
//			   a valid header. The file pointer now points to
//			   the first entry in the log file.
//		S_FALSE - the file has a valid header but the version
//				  of the file is newer than this library code.
//				  The caller should not try to overwrite the
//				  file's contents.
//		Others (failure) - the header is invalid or there was
//						   a problem accessing the file.  The
//						   file should be deleted.
HRESULT ValidateFileHeader(HANDLE hFile, BOOL fCheckHeader, BOOL fFixHeader)
{
	ULHEADER ulheader;
	DWORD dwBytes;
	HRESULT hr = E_FAIL;

	LOG_Block("ValidateFileHeader");

	if (fCheckHeader)
	{
		DWORD dwFileSize = GetFileSize(hFile, NULL);
		// Log file existed before we opened it
		if (INVALID_FILE_SIZE == dwFileSize)
		{
			hr = HRESULT_FROM_WIN32(GetLastError());
			LOG_ErrorMsg(hr);
		}
		else if (1024 * 100 < dwFileSize)	// no more than 100Kbytes
		{
			LOG_Error(_T("too many stale entries in cache."));
		}
		else if (!ReadFile(hFile, &ulheader, sizeof(ulheader), &dwBytes, NULL))
		{
			// We failed to read the header.  We must then fix up the
			// header.
			hr = HRESULT_FROM_WIN32(GetLastError());
			LOG_ErrorMsg(hr);
		}
		else if (sizeof(ulheader) == dwBytes)
		{
			if (CACHE_FILE_VERSION < ulheader.wVersion)
			{
				// A log file of newer version already exists.
				// We should not mess it up with an entry of older
				// format.  The query string will not be saved.
				LOG_Internet(_T("log file is of a newer version. operation cancelled."));
				return S_FALSE;
			}

			if (CACHE_FILE_VERSION == ulheader.wVersion)
			{
				// Correct version number.  We're done.
				return S_OK;
			}
			// else
				// out-dated header
				// We don't care about the entries in it.  We will replace everything
				// in order to fix the header.
		}
		// else
			// incorrect header size
			// We don't care about the entries in it.  We will replace everything
			// in order to fix the header.

		if (!fFixHeader)
		{
			return hr;
		}

		// Truncate the file to zero byte.
		if (INVALID_SET_FILE_POINTER == SetFilePointer(
										hFile,
										0,
										NULL,
										FILE_BEGIN) ||
			!SetEndOfFile(hFile))
		{
			// Nothing we can do if we failed to clear the
			// contents of the file in order to fix it up.
			hr = HRESULT_FROM_WIN32(GetLastError());
			LOG_ErrorMsg(hr);
			return hr;
		}
	}
	else if (!fFixHeader)
	{
		// The caller needs to pick at least one operation.
		return E_INVALIDARG;
	}


	// Assume we are at the beginning of the file.
	// We need to (re)initialize the file.
	if (fFixHeader)
	{
		ZeroMemory(&ulheader, sizeof(ulheader));

		ulheader.wVersion = CACHE_FILE_VERSION;
		if (!WriteFile(hFile, &ulheader, sizeof(ulheader), &dwBytes, NULL))
		{
			hr = HRESULT_FROM_WIN32(GetLastError());
			LOG_ErrorMsg(hr);
			return hr;
		}
		else if (sizeof(ulheader) != dwBytes)
		{
			LOG_Error(_T("Failed to write file header (%d bytes VS %d bytes)"), sizeof(ulheader), dwBytes);
			return E_FAIL;
		}
	}

	return S_OK;
}