//======================================================================= // // 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= // &C= // &A= // &I= // &D= // &P= // &L= // &S= // &E= // &M= // &X= // where // 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. // a string that identifies the entity that performed // 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 // 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 // a string that identifies an update item. // a string that identifies either a device's PNPID when // device driver not found during detection; or a // PNPID/CompatID used by item for activity // if the item is a device driver. // a string that identifies the platform of the running // OS and processor architecture. The class will // compute this value for the pingback. // a string that identifies the language of the OS // binaries. The class will compute this value for the // pingback. // a letter that specifies the status that 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 // a 32-bit error code in hex (w/o "0x" as prefix). // a string that provides additional information for the // status . // a 32-bit random value in hex for overriding proxy // caching. This class will compute this value for // each pingback. // //======================================================================= #include #include // ZeroMemory() #include // PathAppend() #include // srand(), rand(), malloc() and free() #include // _ftime() and _timeb #include // malloc() and free() #include // GetIndustryUpdateDirectory() #include // LOG_Block, LOG_ErrorMsg, LOG_Error and LOG_Internet #include // USES_IU_CONVERSION, W2T() and T2W() #include // LookupLocaleString() #include // DownloadFile() #include // PathCchAppend() #include // SafeFreeNULL() #include #include // 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> 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= 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; }