/*-------------------------------------------------------*/ //Copyright (c) 1997 Microsoft Corporation // //Module Name: Url Tracking Log Interfaces // // Urltrack.cpp // // //Author: // // Pei-Hwa Lin (peihwal) 19-March-97 // //Environment: // // User Mode - Win32 // //Revision History: // 5/13/97 due to cache container type change, allow // OPEN_ALWAYS when CreateFile // 5/14/97 remove IsOnTracking, TRACK_ALL, unused code /*-------------------------------------------------------*/ #include "priv.h" #include #include "basesb.h" #include "bindcb.h" #include const WCHAR c_szPropURL[] = L"HREF"; const WCHAR c_szProptagName[] = L"Item"; const TCHAR c_szLogContainer[] = TEXT("Log"); #define MY_MAX_STRING_LEN 512 //--------------------------------------------------------------------------- // // IUnknown interfaces // //--------------------------------------------------------------------------- HRESULT CUrlTrackingStg :: QueryInterface(REFIID riid, PVOID *ppvObj) { HRESULT hr = E_NOINTERFACE; *ppvObj = NULL; if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IUrlTrackingStg)) { AddRef(); *ppvObj = (LPVOID) SAFECAST(this, IUrlTrackingStg *); hr = S_OK; } return hr; } ULONG CUrlTrackingStg :: AddRef(void) { _cRef ++; return _cRef; } ULONG CUrlTrackingStg :: Release(void) { ASSERT(_cRef > 0); _cRef--; if (!_cRef) { //time to go bye bye delete this; return 0; } return _cRef; } //--------------------------------------------------------------------------- // // C'tor/D'tor // //--------------------------------------------------------------------------- CUrlTrackingStg :: CUrlTrackingStg() { _hFile = NULL; _pRecords = NULL; _lpPfx = NULL; } CUrlTrackingStg :: ~CUrlTrackingStg() { // browser exit while (_pRecords) { OnUnload(_pRecords->pthisUrl); }; if (_lpPfx) { GlobalFree(_lpPfx); _lpPfx = NULL; } if (_hFile) { CloseHandle(_hFile); _hFile = NULL; } } //--------------------------------------------------------------------------- // // Helper functions // //--------------------------------------------------------------------------- LRecord * CUrlTrackingStg :: AddNode() { LRecord* pTemp; LRecord* pNew = NULL; pNew = (LRecord *)LocalAlloc(LPTR, sizeof(LRecord)); if (pNew == NULL) return NULL; pNew->pNext = NULL; if (_pRecords == NULL) { //special case for first node _pRecords = pNew; } else { for (pTemp = _pRecords; pTemp->pNext; pTemp = pTemp->pNext); pTemp->pNext = pNew; } return pNew; } void CUrlTrackingStg :: DeleteFirstNode() { LRecord *pTemp; if (!_pRecords) return; pTemp = _pRecords; _pRecords = pTemp->pNext; delete [] pTemp->pthisUrl; LocalFree(pTemp); pTemp = NULL; return; } void CUrlTrackingStg :: DeleteCurrentNode(LRecord *pThis) { LRecord *pPrev; if (_pRecords == pThis) { DeleteFirstNode(); return; } pPrev = _pRecords; do { if (pPrev->pNext == pThis) { pPrev->pNext = pThis->pNext; delete [] pThis->pthisUrl; LocalFree(pThis); pThis = NULL; break; } pPrev = pPrev->pNext; } while (pPrev); return; } // // return Current node by comparing url strings // LRecord* CUrlTrackingStg :: FindCurrentNode ( IN LPCTSTR lpUrl ) { LRecord* pThis = NULL; ASSERT(_pRecords); if (!_pRecords) // missed OnLoad return NULL; pThis = _pRecords; do { if (!StrCmpI(lpUrl, pThis->pthisUrl)) break; pThis = pThis->pNext; } while (pThis); return pThis; } void CUrlTrackingStg :: DetermineAppModule() { TCHAR szModule[MAX_PATH]; LPTSTR szExt; if (GetModuleFileName(NULL, szModule, MAX_PATH)) { szExt = PathFindExtension(szModule); TraceMsg(0, "tracking: AppModule %s", szModule); if (StrCmpI(szExt, TEXT(".SCR")) == 0) _fScreenSaver = TRUE; else _fScreenSaver = FALSE; } else _fScreenSaver = FALSE; _fModule = TRUE; } //--------------------------------------------------------------------------- // // OnLoad(LPTSTR lpUrl, BRMODE context, BOOL fUseCache) // a new page is loaded // this function will remember time entering this page, context browsing // from and page URL string. // (lpUrl does NOT contain "track:" prefix) //--------------------------------------------------------------------------- HRESULT CUrlTrackingStg :: OnLoad ( IN LPCTSTR lpUrl, IN BRMODE ContextMode, IN BOOL fUseCache ) { HRESULT hr = E_OUTOFMEMORY; SYSTEMTIME st; LRecord* pNewNode = NULL; GetLocalTime(&st); pNewNode = AddNode(); if (!pNewNode) return hr; int cch = lstrlen(lpUrl)+1; pNewNode->pthisUrl = (LPTSTR)LocalAlloc(LPTR, cch * sizeof(TCHAR)); if (pNewNode->pthisUrl == NULL) return hr; // store log info hr = StringCchCopy(pNewNode->pthisUrl, cch, lpUrl); if (SUCCEEDED(hr)) { if (!_fModule) DetermineAppModule(); // if it's from SS, the fullscreen flag will be set, // need to override ContextMode passed in if (_fScreenSaver) pNewNode->Context = BM_SCREENSAVER; else pNewNode->Context = (ContextMode > BM_THEATER) ? BM_UNKNOWN : ContextMode; BYTE cei[MAX_CACHE_ENTRY_INFO_SIZE]; LPINTERNET_CACHE_ENTRY_INFO pcei = (LPINTERNET_CACHE_ENTRY_INFO)cei; DWORD cbcei = MAX_CACHE_ENTRY_INFO_SIZE; if (GetUrlCacheEntryInfo(lpUrl, pcei, &cbcei)) pNewNode->fuseCache = (pcei->dwHitRate - 1) ? TRUE : FALSE; // off 1 by download else pNewNode->fuseCache = 0; SystemTimeToFileTime(&st, &(pNewNode->ftIn)); } return hr; } //--------------------------------------------------------------------------- // // OnUnLoad(LPTSTR lpUrl) // current page is unloaded // 1)find url cache entry and get file handle // 2)calculate total time duration visiting this page // 3)commit delta log string to file cache entry // (lpUrl contains "Tracking: " prefix) // //--------------------------------------------------------------------------- HRESULT CUrlTrackingStg :: OnUnload ( IN LPCTSTR lpUrl ) { HRESULT hr = E_FAIL; LPTSTR lpPfxUrl = NULL; LRecord* pNode = NULL;; SYSTEMTIME st; LPINTERNET_CACHE_ENTRY_INFO pce = NULL; TCHAR lpFile[MAX_PATH]; // GetLocalTime(&st); pNode = FindCurrentNode(lpUrl); if (!pNode) { TraceMsg(DM_ERROR, "CUrlTrackingStg: OnUnload (cannot find internal tracking log"); return hr; } //QueryCacheEntry() and OpenLogFile() can be combined in one if CacheAPI supports //WriteUrlCacheEntryStream() ConvertToPrefixedURL(lpUrl, &lpPfxUrl); if (!lpPfxUrl) { return E_OUTOFMEMORY; } pce = QueryCacheEntry(lpPfxUrl); if (!pce) { TraceMsg(DM_ERROR, "CUrlTrackingStg: OnUnload (cannot find url cache entry)"); DeleteCurrentNode(pNode); // free pce GlobalFree(lpPfxUrl); lpPfxUrl = NULL; return hr; } // work around -- begin hr = WininetWorkAround(lpPfxUrl, pce->lpszLocalFileName, &lpFile[0]); if (FAILED(hr)) { TraceMsg(DM_ERROR, "CUrlTrackingStg: OnUnload (failed to work around wininet)"); DeleteCurrentNode(pNode); if (_hFile) { CloseHandle(_hFile); _hFile = NULL; } GlobalFree(lpPfxUrl); lpPfxUrl = NULL; return hr; } hr = UpdateLogFile(pNode, &st); // commit change to cache if(SUCCEEDED(hr)) { hr = (CommitUrlCacheEntry(lpPfxUrl, lpFile, // pce->ExpireTime, //ExpireTime pce->LastModifiedTime, //LastModifiedTime pce->CacheEntryType, NULL, //lpHeaderInfo 0, //dwHeaderSize NULL, //lpszFileExtension 0) ) ? //reserved S_OK : E_FAIL; } // work around -- end DeleteCurrentNode(pNode); // free pce GlobalFree(pce); pce = NULL; GlobalFree(lpPfxUrl); lpPfxUrl = NULL; return hr; } //--------------------------------------------------------------------------- // // Cache helper funcitons // This is a workaround for Wininet cache // Later when we commit change to URL cache will fail if localFile size is changed // [IN] lpszSourceUrlName and lpszLocalFileName remain the same when calling // this routine // [OUT] new local file name // //--------------------------------------------------------------------------- HRESULT CUrlTrackingStg :: WininetWorkAround(LPCTSTR lpszUrl, LPCTSTR lpOldFile, LPTSTR lpFile) { HRESULT hr = E_FAIL; ASSERT(!_hFile); if (!CreateUrlCacheEntry(lpszUrl, 512, TEXT("log"), lpFile, 0)) return E_FAIL; if (lpOldFile) { if (!CopyFile(lpOldFile, lpFile, FALSE)) return E_FAIL; DeleteFile(lpOldFile); } _hFile = OpenLogFile(lpFile); if (_hFile != INVALID_HANDLE_VALUE) _hFile = NULL; return (_hFile) ? S_OK : E_FAIL; } LPINTERNET_CACHE_ENTRY_INFO CUrlTrackingStg :: QueryCacheEntry ( IN LPCTSTR lpUrl ) { // get cache entry info LPINTERNET_CACHE_ENTRY_INFO lpCE = NULL; DWORD dwEntrySize; BOOL bret = FALSE; lpCE = (LPINTERNET_CACHE_ENTRY_INFO)GlobalAlloc(LPTR, MAX_CACHE_ENTRY_INFO_SIZE); if (lpCE) { dwEntrySize = MAX_CACHE_ENTRY_INFO_SIZE; while (!(bret = GetUrlCacheEntryInfo(lpUrl, lpCE, &dwEntrySize))) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { GlobalFree(lpCE); lpCE = (LPINTERNET_CACHE_ENTRY_INFO)GlobalAlloc(LPTR, dwEntrySize); if (!lpCE) break; } else break; } } if (!bret && lpCE) { GlobalFree(lpCE); lpCE = NULL; SetLastError(ERROR_FILE_NOT_FOUND); } return lpCE; } //--------------------------------------------------------------------------- // // File helper funcitons // //--------------------------------------------------------------------------- // // 1)open log file // 2)move file pointer to end of file // HANDLE CUrlTrackingStg :: OpenLogFile ( IN LPCTSTR lpFileName ) { HANDLE hFile = NULL; hFile = CreateFile(lpFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, // | FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (hFile == INVALID_HANDLE_VALUE) return NULL; return hFile; } const TCHAR c_szLogFormat[] = TEXT("hh':'mm':'ss"); const LPTSTR c_szMode[] = { TEXT("N"), // normal browsing TEXT("S"), // screen saver TEXT("D"), // desktop component TEXT("T"), // theater mode TEXT("U"), // unknown }; HRESULT CUrlTrackingStg :: UpdateLogFile ( IN LRecord* pNode, IN SYSTEMTIME* pst ) { FILETIME ftOut; DWORD dwWritten= 0; HRESULT hr = E_FAIL; ULARGE_INTEGER ulIn, ulOut, ulTotal; ASSERT(_hFile); // calculate delta of time SystemTimeToFileTime(pst, &ftOut); // #34829: use 64-bit calculation ulIn.LowPart = pNode->ftIn.dwLowDateTime; ulIn.HighPart = pNode->ftIn.dwHighDateTime; ulOut.LowPart = ftOut.dwLowDateTime; ulOut.HighPart = ftOut.dwHighDateTime; QUAD_PART(ulTotal) = QUAD_PART(ulOut) - QUAD_PART(ulIn); ftOut.dwLowDateTime = ulTotal.LowPart; ftOut.dwHighDateTime = ulTotal.HighPart; // log string: timeEnter+Duration SYSTEMTIME stOut, stIn; TCHAR lpLogString[MY_MAX_STRING_LEN]; TCHAR pTimeIn[10], pTimeOut[10]; FileTimeToSystemTime(&ftOut, &stOut); FileTimeToSystemTime(&(pNode->ftIn), &stIn); GetTimeFormat(LOCALE_SYSTEM_DEFAULT, TIME_FORCE24HOURFORMAT, &stIn, c_szLogFormat, pTimeIn, 10); GetTimeFormat(LOCALE_SYSTEM_DEFAULT, TIME_FORCE24HOURFORMAT, &stOut, c_szLogFormat, pTimeOut, 10); // #34832: add Date in logs // #28266: add LFCR in logs lpLogString[0] = '\0'; hr = StringCchPrintf(lpLogString, ARRAYSIZE(lpLogString), TEXT("%s %d %.2d-%.2d-%d %s %s\r\n"), c_szMode[pNode->Context], pNode->fuseCache, stIn.wMonth, stIn.wDay, stIn.wYear, pTimeIn, pTimeOut); if (SUCCEEDED(hr)) { // move file pointer to end if (0xFFFFFFFF == SetFilePointer(_hFile, 0, 0, FILE_END)) { CloseHandle(_hFile); _hFile = NULL; return hr; } // write ANSI string to file char szLogInfo[MY_MAX_STRING_LEN]; SHTCharToAnsi(lpLogString, szLogInfo, ARRAYSIZE(szLogInfo)); hr = (WriteFile(_hFile, szLogInfo, lstrlenA(szLogInfo), &dwWritten, NULL)) ? S_OK : E_FAIL; CloseHandle(_hFile); _hFile = NULL; } return hr; } //----------------------------------------------------------------------------- // // ReadTrackingPrefix // // read prefix string from registry //----------------------------------------------------------------------------- void CUrlTrackingStg :: ReadTrackingPrefix(void) { DWORD cbPfx = 0; struct { INTERNET_CACHE_CONTAINER_INFO cInfo; TCHAR szBuffer[MAX_PATH+MAX_PATH]; } ContainerInfo; DWORD dwModified, dwContainer; HANDLE hEnum; dwContainer = sizeof(ContainerInfo); hEnum = FindFirstUrlCacheContainer(&dwModified, &ContainerInfo.cInfo, &dwContainer, 0); if (hEnum) { for (;;) { if (!StrCmpI(ContainerInfo.cInfo.lpszName, c_szLogContainer)) { DWORD cch = lstrlen(ContainerInfo.cInfo.lpszCachePrefix)+1; ASSERT(ContainerInfo.cInfo.lpszCachePrefix[0]); _lpPfx = (LPTSTR)GlobalAlloc(LPTR, cch * sizeof(TCHAR)); if (!_lpPfx) SetLastError(ERROR_OUTOFMEMORY); StringCchCopy(_lpPfx, cch, ContainerInfo.cInfo.lpszCachePrefix); break; } dwContainer = sizeof(ContainerInfo); if (!FindNextUrlCacheContainer(hEnum, &ContainerInfo.cInfo, &dwContainer)) { // This code used to check GetLastError() for ERROR_NO_MORE_ITEMS before // it would break. Well, that could put us in an infinite loop if the // reason for failure were something else (like insufficient buffer) because // wininet would not move forward in it's enumeration and we would not // have done anything to address the error. break; } } FindCloseUrlCache(hEnum); } } // caller must free lplpPrefixedUrl BOOL CUrlTrackingStg :: ConvertToPrefixedURL(LPCTSTR lpszUrl, LPTSTR *lplpPrefixedUrl) { BOOL bret = FALSE; ASSERT(lpszUrl); if (!lpszUrl) return bret; //ASSERT(lplpPrefixedUrl); if (!_lpPfx) ReadTrackingPrefix(); if (_lpPfx) { int len = lstrlen(lpszUrl) + lstrlen(_lpPfx) + 1; *lplpPrefixedUrl = (LPTSTR)GlobalAlloc(LPTR, len * sizeof(TCHAR)); if (*lplpPrefixedUrl) { bret = SUCCEEDED(StringCchPrintf(*lplpPrefixedUrl, len, TEXT("%s%s"), _lpPfx, lpszUrl)); } } return bret; }