You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
431 lines
13 KiB
431 lines
13 KiB
// 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<CBrowserCap *> CacheMapT;
|
|
|
|
static CacheMapT g_strmapBrowsCapINI; // cache of BrowsCap objects
|
|
static TVector<String> 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<String>::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<CBrowserCap>;
|
|
|
|
// 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;
|
|
}
|