// CapMap.cpp: implementation of the CCapMap class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "asptlb.h" #include "context.h" #include "BrwCap.h" #include "CapMap.h" #define MAX_RESSTRINGSIZE 512 #ifdef DBG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif // Global Browser Capabilities Cache. // // This is a doubly indexed list -- // the outer level contains the HTTP_USER_AGENT string. The sub-array is the property in question // // Example: g_strmapBrowsCapINI["Mozilla 3.0"]["VBScript"] retrieves VBScript property of browser // "Mozilla 3.0". (of course, in practice HTTP_USER_AGENT strings are quite long.) // // // A note about the data structure choice: // // Many of the keys in BrowsCap.INI are very similiar to each other. Examples: // // [Mozilla/2.0 (compatible; MSIE 3.0B3; Windows 95)] // [Mozilla/2.0 (compatible; MSIE 3.0B3; Windows NT)] // // or // // [Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)] // [Mozilla/1.22 (compatible; MSIE 2.0c; Windows 95)] // // It's likely that excessive hash collisions could occur (these are hardly random keys!), especially // with small hash modulus (and the table size would be relatively small.) Therefore, the binary search // array of the pre-existing TStringMap class seems best. // // The subkeys that store the properties could probably be hash tables, but there are so few of them, // it probably does not matter. We also use the TStringMap class purely for convenience (it happens to // exist.) // // UNDONE: Cleanup must Release() the pointers (since they are not "smart" CComPtr's) // typedef TSafeStringMap CacheMapT; static CacheMapT g_strmapBrowsCapINI; // cache of BrowsCap objects static TVector g_rgstrWildcard; // list of wildcards in BrowsCap.INI static CReadWrite g_rwWildcardLock; // lock for wildcard array //--------------------------------------------------------------------- // read in wildcards from browscap.ini into g_rgstrWildcard //--------------------------------------------------------------------- void ReadWildcards(const String &strIniFile) { // PERF NOTE: caller should check if rgstrWildcard[] is empty before // calling this function. However, here we do one extra check // when we have the lock because caller should not bother to // secure a write lock when checking rgstrWildcard[]. // g_rwWildcardLock.EnterWriter(); if (g_rgstrWildcard.size() != 0) { g_rwWildcardLock.ExitWriter(); return; } // first get all of the profiles sections into a buffer DWORD dwAllocSize = 16384; TCHAR *szBuffer = new TCHAR[dwAllocSize]; *szBuffer = _T('\0'); DWORD dwSize; // ATLTRACE("ReadWildcards(%s)\n", strIniFile.c_str()); while ((dwSize = GetPrivateProfileSectionNames(szBuffer, dwAllocSize, strIniFile.c_str())) == dwAllocSize-2 && dwSize > 0) { // reallocate the buffer and try again delete[] szBuffer; szBuffer = new TCHAR[dwAllocSize *= 2]; *szBuffer = _T('\0'); } if (dwSize == 0) ATLTRACE("ReadWildcards(%s) failed, err=%d\n", strIniFile.c_str(), GetLastError()); TCHAR *szSave = szBuffer; // now put all wild-card containing entries into the list while( *szBuffer != _T('\0') ) { if (_tcspbrk(szBuffer, "[*?") != NULL) g_rgstrWildcard.push_back(szBuffer); // advance to the beginning of the next string while (*szBuffer != _T('\0')) szBuffer = CharNext(szBuffer); // now advance once more to get to the next string ++szBuffer; } delete[] szSave; g_rwWildcardLock.ExitWriter(); } //--------------------------------------------------------------------- // compare names to templates, *, ?, [, ], not legal filename characters // // Also compute # of matching wildcard characters. // FOR THIS TO WORK: caller must pass in an initialized counter! //--------------------------------------------------------------------- bool match( LPCTSTR szPattern, LPCTSTR szSubject, int *pcchWildcardMatched) { LPTSTR rp; _TCHAR tc; if (*szPattern == '*') { ++szPattern; do { int cchWildcardSubMatch = 0; if (match(szPattern, szSubject, &cchWildcardSubMatch) == true) { *pcchWildcardMatched += cchWildcardSubMatch; return true; } } while (++*pcchWildcardMatched, *szSubject++ != '\0'); } else if (*szSubject == '\0') return *szPattern == '\0'; else if (*szPattern == '[' && (rp = _tcschr(szPattern, ']')) != NULL) { while (*++szPattern != ']') if ((tc = *szPattern) == *szSubject || (szPattern[1] == '-' && (*(szPattern += 2) >= *szSubject && tc <= *szSubject))) { ++*pcchWildcardMatched; return match(rp + 1, ++szSubject, pcchWildcardMatched); } return false; } else if (*szPattern == '?') { ++*pcchWildcardMatched; return match(++szPattern, ++szSubject, pcchWildcardMatched); } else if (tolower(*szPattern) == tolower(*szSubject)) return match(++szPattern, ++szSubject, pcchWildcardMatched); return false; } //--------------------------------------------------------------------- // FindBrowser // // match the User Agent against all the wildcards in browscap.ini and // return the best match. "Best Match" is defined here to mean the match // requiring the fewest amount of wildcard substitutions. //--------------------------------------------------------------------- #define INT_MAX int(unsigned(~0) >> 1) String FindBrowser(const String &strUserAgent, const String &strIniFile) { TVector::iterator iter; String strT; if (g_rgstrWildcard.size() == 0) ReadWildcards(strIniFile); g_rwWildcardLock.EnterReader(); int cchWildMatchMin = INT_MAX; for (iter = g_rgstrWildcard.begin(); iter < g_rgstrWildcard.end(); ++iter) { int cchWildMatchCurrent = 0; if (match((*iter).c_str(), strUserAgent.c_str(), &cchWildMatchCurrent) && cchWildMatchCurrent < cchWildMatchMin) { cchWildMatchMin = cchWildMatchCurrent; strT = *iter; } } g_rwWildcardLock.ExitReader(); // Backward compatibility: If nothing matches, then use // "Default Browser Capability Settings". In the new // model, the catch all rule, "*" can also be used. // if (strT.length() == 0) strT = "Default Browser Capability Settings"; return strT; } //--------------------------------------------------------------------- // CCapNotify //--------------------------------------------------------------------- CCapNotify::CCapNotify() : m_isNotified(0) { } void CCapNotify::Notify() { ::InterlockedExchange( &m_isNotified, 1 ); } bool CCapNotify::IsNotified() { return ( ::InterlockedExchange( &m_isNotified, 0 ) ? true : false ); } //--------------------------------------------------------------------- // CCapMap //--------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CCapMap::CCapMap() { static const String cszIniFile = _T("Browscap.ini"); // get the path to the inifile containing the browser cap info _TCHAR szModule[ _MAX_PATH ]; ::GetModuleFileName(_Module.GetModuleInstance(), szModule, sizeof(szModule)); ATLTRACE("CapMap: Module(%s)\n", szModule); // remove the filename and tack on the ini file name _TCHAR* pch = _tcsrchr(szModule, '\\'); if (pch == NULL) { // the path should have at least one backslash _ASSERT(0); pch = szModule; } *(pch+1) = _T('\0'); m_strIniFile = szModule + cszIniFile; ATLTRACE("CCapMap::CCapMap(%s)\n", m_strIniFile.c_str()); // start monitoring the file m_pSink = new CCapNotify(); } void CCapMap::StartMonitor() { if ( _Module.Monitor() ) { _Module.Monitor()->MonitorFile( m_strIniFile.c_str(), m_pSink ); ATLTRACE("CCapMap::StartMonitor(%s)\n", m_strIniFile.c_str()); } else ATLTRACE("CCapMap::StartMonitor -- no monitor\n"); } void CCapMap::StopMonitor() { if ( _Module.Monitor() ) { _Module.Monitor()->StopMonitoringFile( m_strIniFile.c_str() ); ATLTRACE("CCapMap::StopMonitor(%s)\n", m_strIniFile.c_str()); } else ATLTRACE("CCapMap::StopMonitor -- no monitor\n"); } CBrowserCap * CCapMap::LookUp( const String& szBrowser) { Refresh(); CLock csT(g_strmapBrowsCapINI); CacheMapT::referent_type &rpBCobj = g_strmapBrowsCapINI[szBrowser]; if (rpBCobj == NULL) { rpBCobj = new CComObject; // Complete construction and AddRef copy we keep in cache. // NOTE: Since caller (class factory) does implicit AddRef via QueryInterface, // the convention of this function is slightly different from COM std. // CALLER IS RESPONSIBLE TO ADDREF RETURNED OBJECT // rpBCobj->FinalConstruct(); rpBCobj->AddRef(); // ATLTRACE("LookUp(%s)\n", szBrowser.c_str()); // Get Browser Properties _TCHAR szSection[DWSectionBufSize]; if (GetPrivateProfileSection ( szBrowser.c_str(), // section szSection, // return buffer DWSectionBufSize, // size of return buffer m_strIniFile.c_str() // .INI name ) == 0) { // If this call fails, that means the default browser does not exist either, so // everything is "unknown". // String szT = FindBrowser(szBrowser, m_strIniFile); if (GetPrivateProfileSection ( szT.c_str(), // section szSection, // return buffer DWSectionBufSize, // size of return buffer m_strIniFile.c_str() // .INI name ) == 0) { ATLTRACE("GPPS(%s) failed, err=%d\n", szT.c_str(), GetLastError()); return rpBCobj; } } // Loop through szSection, which contains all the key=value pairs and add them // to the browser instance property list. If we find a "Parent=" Key, save the // value to add the parent's properties later. // TCHAR *szParent; do { szParent = NULL; TCHAR *szKeyAndValue = szSection; while (*szKeyAndValue) { TCHAR *szKey = szKeyAndValue; // save the key TCHAR *szValue = _tcschr(szKey, '='); // find address of value part (-1) szKeyAndValue += _tcslen(szKeyAndValue) + 1; // advance KeyAndValue to the next pair if (szValue == NULL) continue; *szValue++ = '\0'; // separate key and value with NUL; advance if (_tcsicmp(szKey, _T("Parent")) == 0) szParent = szValue; else rpBCobj->AddProperty(szKey, szValue); } // We stored all the attributes on this level. Ascend to parent level (if it exists) if (szParent) { if (GetPrivateProfileSection ( szParent, // section szSection, // return buffer DWSectionBufSize, // size of return buffer m_strIniFile.c_str() // .INI name ) == 0) { // If this call fails, quit now. // String szT = FindBrowser(szParent, m_strIniFile); if (GetPrivateProfileSection ( szT.c_str(), // section szSection, // return buffer DWSectionBufSize, // size of return buffer m_strIniFile.c_str() // .INI name ) == 0) { ATLTRACE("GPPS(%s) failed, err=%d\n", szT.c_str(), GetLastError()); return rpBCobj; } } } } while (szParent); } return rpBCobj; } //--------------------------------------------------------------------------- // // Refresh will check to see if the cached information is out of date with // the ini file. If so, the cached will be purged // //--------------------------------------------------------------------------- bool CCapMap::Refresh() { bool rc = false; if ( m_pSink->IsNotified() ) { // Clear the cache CLock csT(g_strmapBrowsCapINI); g_strmapBrowsCapINI.clear(); rc = true; // clear the list of wildcards. // NOTE: each browser request creates new CCapMap object. // the constructor will see the size is zero and reconstruct g_rwWildcardLock.EnterWriter(); g_rgstrWildcard.clear(); g_rwWildcardLock.ExitWriter(); } return rc; }