|
|
/*-------------------------------------------------------*/ //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 <wininet.h>
#include "basesb.h"
#include "bindcb.h"
#include <strsafe.h>
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; }
|