windows-nt/Source/XPSP1/NT/enduser/windows.com/lib/urllogging/urllogging.cpp
2020-09-26 16:20:57 +08:00

1556 lines
38 KiB
C++

//=======================================================================
//
// 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;
}