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.
1598 lines
45 KiB
1598 lines
45 KiB
#include "msrating.h"
|
|
#include <npassert.h>
|
|
#include "array.h"
|
|
#include "msluglob.h"
|
|
#include "parselbl.h"
|
|
#include "debug.h"
|
|
#include <convtime.h>
|
|
#include <wininet.h>
|
|
|
|
extern BOOL LoadWinINet();
|
|
|
|
|
|
COptionsBase::COptionsBase()
|
|
{
|
|
m_cRef = 1;
|
|
m_timeUntil = 0xffffffff; /* as far in the future as possible */
|
|
m_fdwFlags = 0;
|
|
m_pszInvalidString = NULL;
|
|
m_pszURL = NULL;
|
|
}
|
|
|
|
|
|
void COptionsBase::AddRef()
|
|
{
|
|
m_cRef++;
|
|
}
|
|
|
|
|
|
void COptionsBase::Release()
|
|
{
|
|
if (!--m_cRef)
|
|
Delete();
|
|
}
|
|
|
|
|
|
void COptionsBase::Delete()
|
|
{
|
|
/* default does nothing when deleting reference */
|
|
}
|
|
|
|
|
|
BOOL COptionsBase::CheckUntil(DWORD timeUntil)
|
|
{
|
|
if (m_timeUntil <= timeUntil)
|
|
{
|
|
m_fdwFlags |= LBLOPT_EXPIRED;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* AppendSlash forces pszString to end in a single slash if it doesn't
|
|
* already. This may produce a technically invalid URL (for example,
|
|
* "http://gregj/default.htm/", but we're only using the result for
|
|
* comparisons against other paths similarly mangled.
|
|
*/
|
|
void AppendSlash(LPSTR pszString)
|
|
{
|
|
LPSTR pszSlash = ::strrchrf(pszString, '/');
|
|
|
|
if (pszSlash == NULL || *(pszSlash + 1) != '\0')
|
|
::strcatf(pszString, "/");
|
|
}
|
|
|
|
|
|
extern BOOL (WINAPI *pfnInternetCrackUrl)(
|
|
IN LPCTSTR lpszUrl,
|
|
IN DWORD dwUrlLength,
|
|
IN DWORD dwFlags,
|
|
IN OUT LPURL_COMPONENTS lpUrlComponents
|
|
);
|
|
extern BOOL (WINAPI *pfnInternetCanonicalizeUrl)(
|
|
IN LPCSTR lpszUrl,
|
|
OUT LPSTR lpszBuffer,
|
|
IN OUT LPDWORD lpdwBufferLength,
|
|
IN DWORD dwFlags
|
|
);
|
|
|
|
|
|
BOOL DoURLsMatch(LPCSTR pszBaseURL, LPCSTR pszCheckURL, BOOL fGeneric)
|
|
{
|
|
/* Buffers to canonicalize URLs into */
|
|
LPSTR pszBaseCanon = new char[INTERNET_MAX_URL_LENGTH + 1];
|
|
LPSTR pszCheckCanon = new char[INTERNET_MAX_URL_LENGTH + 1];
|
|
|
|
if (pszBaseCanon != NULL && pszCheckCanon != NULL)
|
|
{
|
|
BOOL fCanonOK = FALSE;
|
|
DWORD cbBuffer = INTERNET_MAX_URL_LENGTH + 1;
|
|
if (pfnInternetCanonicalizeUrl(pszBaseURL, pszBaseCanon, &cbBuffer, ICU_ENCODE_SPACES_ONLY))
|
|
{
|
|
cbBuffer = INTERNET_MAX_URL_LENGTH + 1;
|
|
if (pfnInternetCanonicalizeUrl(pszCheckURL, pszCheckCanon, &cbBuffer, ICU_ENCODE_SPACES_ONLY))
|
|
{
|
|
fCanonOK = TRUE;
|
|
}
|
|
}
|
|
if (!fCanonOK)
|
|
{
|
|
delete pszBaseCanon;
|
|
pszBaseCanon = NULL;
|
|
delete pszCheckCanon;
|
|
pszCheckCanon = NULL;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
UINT cbBaseURL = strlenf(pszBaseCanon) + 1;
|
|
|
|
LPSTR pszBaseUrlPath = new char[cbBaseURL];
|
|
LPSTR pszBaseExtra = new char[cbBaseURL];
|
|
|
|
CHAR szBaseHostName[INTERNET_MAX_HOST_NAME_LENGTH];
|
|
CHAR szBaseUrlScheme[20]; // reasonable limit
|
|
|
|
UINT cbCheckURL = strlenf(pszCheckCanon) + 1;
|
|
|
|
LPSTR pszCheckUrlPath = new char[cbCheckURL];
|
|
LPSTR pszCheckExtra = new char[cbCheckURL];
|
|
|
|
CHAR szCheckHostName[INTERNET_MAX_HOST_NAME_LENGTH];
|
|
CHAR szCheckUrlScheme[20]; // reasonable limit
|
|
|
|
BOOL fOK = FALSE;
|
|
|
|
if (pszBaseUrlPath != NULL &&
|
|
pszBaseExtra != NULL &&
|
|
pszCheckUrlPath != NULL &&
|
|
pszCheckExtra != NULL)
|
|
{
|
|
|
|
URL_COMPONENTS ucBase, ucCheck;
|
|
|
|
memset(&ucBase, 0, sizeof(ucBase));
|
|
ucBase.dwStructSize = sizeof(ucBase);
|
|
ucBase.lpszScheme = szBaseUrlScheme;
|
|
ucBase.dwSchemeLength = sizeof(szBaseUrlScheme);
|
|
ucBase.lpszHostName = szBaseHostName;
|
|
ucBase.dwHostNameLength = sizeof(szBaseHostName);
|
|
ucBase.lpszUrlPath = pszBaseUrlPath;
|
|
ucBase.dwUrlPathLength = cbBaseURL;
|
|
ucBase.lpszExtraInfo = pszBaseExtra;
|
|
ucBase.dwExtraInfoLength = cbBaseURL;
|
|
|
|
memset(&ucCheck, 0, sizeof(ucCheck));
|
|
ucCheck.dwStructSize = sizeof(ucCheck);
|
|
ucCheck.lpszScheme = szCheckUrlScheme;
|
|
ucCheck.dwSchemeLength = sizeof(szCheckUrlScheme);
|
|
ucCheck.lpszHostName = szCheckHostName;
|
|
ucCheck.dwHostNameLength = sizeof(szCheckHostName);
|
|
ucCheck.lpszUrlPath = pszCheckUrlPath;
|
|
ucCheck.dwUrlPathLength = cbCheckURL;
|
|
ucCheck.lpszExtraInfo = pszCheckExtra;
|
|
ucCheck.dwExtraInfoLength = cbCheckURL;
|
|
|
|
if (pfnInternetCrackUrl(pszBaseCanon, 0, 0, &ucBase) &&
|
|
pfnInternetCrackUrl(pszCheckCanon, 0, 0, &ucCheck))
|
|
{
|
|
/* Scheme and host name must always match */
|
|
if (!stricmpf(ucBase.lpszScheme, ucCheck.lpszScheme) &&
|
|
!stricmpf(ucBase.lpszHostName, ucCheck.lpszHostName))
|
|
{
|
|
/* For extra info, just has to match exactly, even for a generic URL. */
|
|
if (!*ucBase.lpszExtraInfo ||
|
|
!stricmpf(ucBase.lpszExtraInfo, ucCheck.lpszExtraInfo))
|
|
{
|
|
AppendSlash(ucBase.lpszUrlPath);
|
|
AppendSlash(ucCheck.lpszUrlPath);
|
|
|
|
/* If not a generic label, path must match exactly too */
|
|
if (!fGeneric)
|
|
{
|
|
if (!stricmpf(ucBase.lpszUrlPath, ucCheck.lpszUrlPath))
|
|
{
|
|
fOK = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UINT cbBasePath = strlenf(ucBase.lpszUrlPath);
|
|
if (!strnicmpf(ucBase.lpszUrlPath, ucCheck.lpszUrlPath, cbBasePath))
|
|
{
|
|
fOK = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
delete pszBaseUrlPath;
|
|
pszBaseUrlPath = NULL;
|
|
delete pszBaseExtra;
|
|
pszBaseExtra = NULL;
|
|
|
|
delete pszCheckUrlPath;
|
|
pszCheckUrlPath = NULL;
|
|
delete pszCheckExtra;
|
|
pszCheckExtra = NULL;
|
|
|
|
delete pszBaseCanon;
|
|
pszBaseCanon = NULL;
|
|
delete pszCheckCanon;
|
|
pszCheckCanon = NULL;
|
|
|
|
return fOK;
|
|
}
|
|
|
|
|
|
BOOL COptionsBase::CheckURL(LPCSTR pszURL)
|
|
{
|
|
if (!(m_fdwFlags & LBLOPT_URLCHECKED))
|
|
{
|
|
m_fdwFlags |= LBLOPT_URLCHECKED;
|
|
|
|
BOOL fInvalid = FALSE;
|
|
|
|
if (pszURL != NULL && m_pszURL != NULL)
|
|
{
|
|
if (LoadWinINet())
|
|
{
|
|
fInvalid = !DoURLsMatch(m_pszURL, pszURL, m_fdwFlags & LBLOPT_GENERIC);
|
|
}
|
|
}
|
|
|
|
if (fInvalid)
|
|
{
|
|
m_fdwFlags |= LBLOPT_WRONGURL;
|
|
}
|
|
}
|
|
|
|
return !(m_fdwFlags & LBLOPT_WRONGURL);
|
|
}
|
|
|
|
|
|
void CDynamicOptions::Delete()
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
|
|
CParsedServiceInfo::CParsedServiceInfo()
|
|
{
|
|
m_pNext = NULL;
|
|
m_poptCurrent = &m_opt;
|
|
m_poptList = NULL;
|
|
m_pszServiceName = NULL;
|
|
m_pszErrorString = NULL;
|
|
m_fInstalled = TRUE; /* assume the best */
|
|
m_pszInvalidString = NULL;
|
|
m_pszCurrent = NULL;
|
|
}
|
|
|
|
|
|
void FreeOptionsList(CDynamicOptions *pList)
|
|
{
|
|
while (pList != NULL)
|
|
{
|
|
CDynamicOptions *pNext = pList->m_pNext;
|
|
delete pList;
|
|
pList = pNext;
|
|
}
|
|
}
|
|
|
|
|
|
CParsedServiceInfo::~CParsedServiceInfo()
|
|
{
|
|
FreeOptionsList(m_poptList);
|
|
}
|
|
|
|
|
|
void CParsedServiceInfo::Append(CParsedServiceInfo *pNew)
|
|
{
|
|
CParsedServiceInfo **ppNext = &m_pNext;
|
|
|
|
while (*ppNext != NULL)
|
|
{
|
|
ppNext = &((*ppNext)->m_pNext);
|
|
}
|
|
|
|
*ppNext = pNew;
|
|
pNew->m_pNext = NULL;
|
|
}
|
|
|
|
|
|
CParsedLabelList::CParsedLabelList()
|
|
{
|
|
m_pszList = NULL;
|
|
m_fRated = FALSE;
|
|
m_pszInvalidString = NULL;
|
|
m_pszURL = NULL;
|
|
m_pszOriginalLabel = NULL;
|
|
m_fDenied = FALSE;
|
|
m_fIsHelper = FALSE;
|
|
m_fNoRating = FALSE;
|
|
m_fIsCustomHelper = FALSE;
|
|
m_pszRatingName = NULL;
|
|
m_pszRatingReason = NULL;
|
|
}
|
|
|
|
|
|
CParsedLabelList::~CParsedLabelList()
|
|
{
|
|
delete m_pszList;
|
|
m_pszList = NULL;
|
|
|
|
CParsedServiceInfo *pInfo = m_ServiceInfo.Next();
|
|
|
|
while (pInfo != NULL)
|
|
{
|
|
CParsedServiceInfo *pNext = pInfo->Next();
|
|
delete pInfo;
|
|
pInfo = pNext;
|
|
}
|
|
|
|
delete m_pszURL;
|
|
m_pszURL = NULL;
|
|
delete m_pszOriginalLabel;
|
|
m_pszOriginalLabel = NULL;
|
|
|
|
delete [] m_pszRatingName;
|
|
m_pszRatingName = NULL;
|
|
delete [] m_pszRatingReason;
|
|
m_pszRatingReason = NULL;
|
|
}
|
|
|
|
|
|
/* SkipWhitespace(&pszString)
|
|
*
|
|
* advances pszString past whitespace characters
|
|
*/
|
|
void SkipWhitespace(LPSTR *ppsz)
|
|
{
|
|
UINT cchWhitespace = ::strspnf(*ppsz, szWhitespace);
|
|
|
|
*ppsz += cchWhitespace;
|
|
}
|
|
|
|
|
|
/* FindTokenEnd(pszStart)
|
|
*
|
|
* Returns a pointer to the end of a contiguous range of similarly-typed
|
|
* characters (whitespace, quote mark, punctuation, or alphanumerics).
|
|
*/
|
|
LPSTR FindTokenEnd(LPSTR pszStart)
|
|
{
|
|
LPSTR pszEnd = pszStart;
|
|
|
|
if (*pszEnd == '\0')
|
|
{
|
|
return pszEnd;
|
|
}
|
|
else if (strchrf(szSingleCharTokens, *pszEnd))
|
|
{
|
|
return ++pszEnd;
|
|
}
|
|
|
|
UINT cch;
|
|
cch = ::strspnf(pszEnd, szWhitespace);
|
|
if (cch > 0)
|
|
{
|
|
return pszEnd + cch;
|
|
}
|
|
|
|
cch = ::strspnf(pszEnd, szExtendedAlphaNum);
|
|
if (cch > 0)
|
|
{
|
|
return pszEnd + cch;
|
|
}
|
|
|
|
return pszEnd; /* unrecognized characters */
|
|
}
|
|
|
|
|
|
/* GetBool(LPSTR *ppszToken, BOOL *pfOut, PICSRulesBooleanSwitch PRBoolSwitch)
|
|
*
|
|
* t-markh 8/98 (
|
|
* added default parameter PRBoolSwitch=PR_BOOLEAN_TRUEFALSE
|
|
* this allows for no modification of existing code, and extension
|
|
* of the GetBool function from true/false to include pass/fail and
|
|
* yes/no. The enumerated type PICSRulesBooleanSwitch is defined
|
|
* in picsrule.h)
|
|
*
|
|
* Parses a boolean value at the given token and returns its value in *pfOut.
|
|
* Legal values are 't', 'f', 'true', and 'false'. If success, *ppszToken
|
|
* is advanced past the boolean token and any following whitespace. If failure,
|
|
* *ppszToken is not modified.
|
|
*
|
|
* pfOut may be NULL if the caller just wants to eat the token and doesn't
|
|
* care about its value.
|
|
*/
|
|
HRESULT GetBool(LPSTR *ppszToken, BOOL *pfOut, PICSRulesBooleanSwitch PRBoolSwitch)
|
|
{
|
|
BOOL bValue;
|
|
|
|
LPSTR pszTokenEnd = FindTokenEnd(*ppszToken);
|
|
|
|
switch(PRBoolSwitch)
|
|
{
|
|
case PR_BOOLEAN_TRUEFALSE:
|
|
{
|
|
if (IsEqualToken(*ppszToken, pszTokenEnd, szShortTrue) ||
|
|
IsEqualToken(*ppszToken, pszTokenEnd, szTrue))
|
|
{
|
|
bValue = TRUE;
|
|
}
|
|
else if (IsEqualToken(*ppszToken, pszTokenEnd, szShortFalse) ||
|
|
IsEqualToken(*ppszToken, pszTokenEnd, szFalse))
|
|
{
|
|
bValue = FALSE;
|
|
}
|
|
else
|
|
{
|
|
TraceMsg( TF_WARNING, "GetBool() - Failed True/False Token Parse at '%s'!", *ppszToken );
|
|
return ResultFromScode(MK_E_SYNTAX);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PR_BOOLEAN_PASSFAIL:
|
|
{
|
|
//szPRShortPass and szPRShortfail are not supported in the
|
|
//official PICSRules spec, but we'll catch them anyway
|
|
|
|
if (IsEqualToken(*ppszToken, pszTokenEnd, szPRShortPass) ||
|
|
IsEqualToken(*ppszToken, pszTokenEnd, szPRPass))
|
|
{
|
|
bValue = PR_PASSFAIL_PASS;
|
|
}
|
|
else if (IsEqualToken(*ppszToken, pszTokenEnd, szPRShortFail) ||
|
|
IsEqualToken(*ppszToken, pszTokenEnd, szPRFail))
|
|
{
|
|
bValue = PR_PASSFAIL_FAIL;
|
|
}
|
|
else
|
|
{
|
|
TraceMsg( TF_WARNING, "GetBool() - Failed Pass/Fail Token Parse at '%s'!", *ppszToken );
|
|
return ResultFromScode(MK_E_SYNTAX);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PR_BOOLEAN_YESNO:
|
|
{
|
|
if (IsEqualToken(*ppszToken, pszTokenEnd, szPRShortYes) ||
|
|
IsEqualToken(*ppszToken, pszTokenEnd, szPRYes))
|
|
{
|
|
bValue = PR_YESNO_YES;
|
|
}
|
|
else if (IsEqualToken(*ppszToken, pszTokenEnd, szPRShortNo) ||
|
|
IsEqualToken(*ppszToken, pszTokenEnd, szPRNo))
|
|
{
|
|
bValue = PR_YESNO_NO;
|
|
}
|
|
else
|
|
{
|
|
TraceMsg( TF_WARNING, "GetBool() - Failed Yes/No Token Parse at '%s'!", *ppszToken );
|
|
return ResultFromScode(MK_E_SYNTAX);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
return(MK_E_UNAVAILABLE);
|
|
}
|
|
}
|
|
|
|
if (pfOut != NULL)
|
|
{
|
|
*pfOut = bValue;
|
|
}
|
|
|
|
*ppszToken = pszTokenEnd;
|
|
SkipWhitespace(ppszToken);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
/* GetQuotedToken(&pszThisToken, &pszQuotedToken)
|
|
*
|
|
* Sets pszQuotedToken to point to the contents of the doublequotes.
|
|
* pszQuotedToken may be NULL if the caller just wants to eat the token.
|
|
* Sets pszThisToken to point to the first character after the closing
|
|
* doublequote.
|
|
* Fails if pszThisToken doesn't start with a doublequote or doesn't
|
|
* contain a closing doublequote.
|
|
* The closing doublequote is replaced with a null terminator, iff the
|
|
* function does not fail.
|
|
*/
|
|
HRESULT GetQuotedToken(LPSTR *ppszThisToken, LPSTR *ppszQuotedToken)
|
|
{
|
|
HRESULT hres = ResultFromScode(MK_E_SYNTAX);
|
|
|
|
LPSTR pszStart = *ppszThisToken;
|
|
if (*pszStart != '\"')
|
|
{
|
|
TraceMsg( TF_WARNING, "GetQuotedToken() - Failed to Find Start Quote at '%s'!", pszStart );
|
|
return hres;
|
|
}
|
|
|
|
pszStart++;
|
|
LPSTR pszEndQuote = strchrf(pszStart, '\"');
|
|
if (pszEndQuote == NULL)
|
|
{
|
|
TraceMsg( TF_WARNING, "GetQuotedToken() - Failed to Find End Quote at '%s'!", pszStart );
|
|
return hres;
|
|
}
|
|
|
|
*pszEndQuote = '\0';
|
|
if (ppszQuotedToken != NULL)
|
|
{
|
|
*ppszQuotedToken = pszStart;
|
|
}
|
|
|
|
*ppszThisToken = pszEndQuote+1;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
BOOL IsEqualToken(LPCSTR pszTokenStart, LPCSTR pszTokenEnd, LPCSTR pszTokenToMatch)
|
|
{
|
|
UINT cbToken = strlenf(pszTokenToMatch);
|
|
|
|
if (cbToken != (UINT)(pszTokenEnd - pszTokenStart) || strnicmpf(pszTokenStart, pszTokenToMatch, cbToken))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* ParseLiteralToken(ppsz, pszToken) tries to match *ppsz against pszToken.
|
|
* If they don't match, an error is returned. If they do match, then *ppsz
|
|
* is advanced past the token and any following whitespace.
|
|
*
|
|
* If ppszInvalid is NULL, then the function is non-destructive in the error
|
|
* path, so it's OK to call ParseLiteralToken just to see if a possible literal
|
|
* token is what's next; if the token isn't found, whatever was there didn't
|
|
* get eaten or anything.
|
|
*
|
|
* If ppszInvalid is not NULL, then if the token doesn't match, *ppszInvalid
|
|
* will be set to *ppsz.
|
|
*/
|
|
HRESULT ParseLiteralToken(LPSTR *ppsz, LPCSTR pszToken, LPCSTR *ppszInvalid)
|
|
{
|
|
LPSTR pszTokenEnd = FindTokenEnd(*ppsz);
|
|
|
|
if (!IsEqualToken(*ppsz, pszTokenEnd, pszToken))
|
|
{
|
|
if (ppszInvalid != NULL)
|
|
{
|
|
*ppszInvalid = *ppsz;
|
|
}
|
|
|
|
// TraceMsg( TF_WARNING, "ParseLiteralToken() - Token '%s' Not Found at '%s'!", pszToken, *ppsz );
|
|
|
|
return ResultFromScode(MK_E_SYNTAX);
|
|
}
|
|
|
|
*ppsz = pszTokenEnd;
|
|
|
|
SkipWhitespace(ppsz);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
/* ParseServiceError parses a service-error construct, once it's been
|
|
* determined that such is the case. m_pszCurrent has been advanced past
|
|
* the 'error' keyword that indicates a service-error.
|
|
*
|
|
* We're pretty flexible about the contents of this stuff. We basically
|
|
* accept anything of the form:
|
|
*
|
|
* 'error' '(' <error string> [quoted explanations] ')' - or -
|
|
* 'error' <error string>
|
|
*
|
|
* without caring too much about what the error string actually is.
|
|
*
|
|
* A format with quoted explanations but without the parens would not be
|
|
* legal, we wouldn't be able to distinguish the explanations from the
|
|
* serviceID of the next service-info.
|
|
*/
|
|
HRESULT CParsedServiceInfo::ParseServiceError()
|
|
{
|
|
BOOL fParen = FALSE;
|
|
HRESULT hres = NOERROR;
|
|
|
|
if (SUCCEEDED(ParseLiteralToken(&m_pszCurrent, szLeftParen, NULL)))
|
|
{
|
|
fParen = TRUE;
|
|
}
|
|
|
|
LPSTR pszErrorEnd = FindTokenEnd(m_pszCurrent); /* find end of error string */
|
|
|
|
m_pszErrorString = m_pszCurrent; /* remember start of error string */
|
|
if (fParen)
|
|
{ /* need to eat explanations */
|
|
m_pszCurrent = pszErrorEnd; /* skip error string to get to explanations */
|
|
SkipWhitespace();
|
|
while (SUCCEEDED(hres))
|
|
{
|
|
hres = GetQuotedToken(&m_pszCurrent, NULL);
|
|
SkipWhitespace();
|
|
}
|
|
}
|
|
|
|
if (fParen)
|
|
{
|
|
hres = ParseLiteralToken(&m_pszCurrent, szRightParen, &m_pszInvalidString);
|
|
}
|
|
else
|
|
{
|
|
hres = NOERROR;
|
|
}
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
*pszErrorEnd = '\0'; /* null-terminate the error string */
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/* ParseNumber parses a numeric token at the specified position. If the
|
|
* number makes sense, the pointer is advanced to the end of the number
|
|
* and past any following whitespace, and the numeric value is returned
|
|
* in *pnOut. Any non-numeric characters are considered to terminate the
|
|
* number without error; it is assumed that higher-level parsing code
|
|
* will eventually reject such characters if they're not supposed to be
|
|
* there.
|
|
*
|
|
* pnOut may be NULL if the caller doesn't care about the number being
|
|
* returned and just wants to eat it.
|
|
*
|
|
* Floating point numbers of the form nnn.nnn are rounded to the next
|
|
* higher integer and returned as such.
|
|
*/
|
|
//t-markh 8/98 - added fPICSRules for line counting support in PICSRules
|
|
HRESULT ParseNumber(LPSTR *ppszNumber, INT *pnOut,BOOL fPICSRules)
|
|
{
|
|
HRESULT hres = ResultFromScode(MK_E_SYNTAX);
|
|
BOOL fNegative = FALSE;
|
|
INT nAccum = 0;
|
|
BOOL fNonZeroDecimal = FALSE;
|
|
BOOL fInDecimal = FALSE;
|
|
BOOL fFoundDigits = FALSE;
|
|
|
|
LPSTR pszCurrent = *ppszNumber;
|
|
|
|
/* Handle one sign character. */
|
|
if (*pszCurrent == '+')
|
|
{
|
|
pszCurrent++;
|
|
}
|
|
else if (*pszCurrent == '-')
|
|
{
|
|
pszCurrent++;
|
|
fNegative = TRUE;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
if (*pszCurrent == '.')
|
|
{
|
|
fInDecimal = TRUE;
|
|
}
|
|
else if (*pszCurrent >= '0' && *pszCurrent <= '9')
|
|
{
|
|
fFoundDigits = TRUE;
|
|
if (fInDecimal)
|
|
{
|
|
if (*pszCurrent > '0')
|
|
{
|
|
fNonZeroDecimal = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nAccum = nAccum * 10 + (*pszCurrent - '0');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
pszCurrent++;
|
|
}
|
|
|
|
if (fFoundDigits)
|
|
{
|
|
hres = NOERROR;
|
|
if (fNonZeroDecimal)
|
|
{
|
|
nAccum++; /* round away from zero if decimal present */
|
|
}
|
|
|
|
if (fNegative)
|
|
{
|
|
nAccum = -nAccum;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
if (pnOut != NULL)
|
|
{
|
|
*pnOut = nAccum;
|
|
}
|
|
|
|
*ppszNumber = pszCurrent;
|
|
if ( fPICSRules == FALSE )
|
|
{
|
|
SkipWhitespace(ppszNumber);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TraceMsg( TF_WARNING, "ParseNumber() - Failed with hres=0x%x at '%s'!", hres, pszCurrent );
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/* ParseExtensionData just needs to get past whatever data was supplied
|
|
* for an extension. The PICS spec implies that it can be recursive, which
|
|
* complicates matters a bit:
|
|
*
|
|
* data :: quoted-ISO-date | quotedURL | number | quotedname | '(' data* ')'
|
|
*
|
|
* Use of recursion here is probably OK, we don't really expect complicated
|
|
* nested extensions all that often, and this function doesn't use a lot of
|
|
* stack or other resources...
|
|
*/
|
|
HRESULT CParsedServiceInfo::ParseExtensionData(COptionsBase *pOpt)
|
|
{
|
|
HRESULT hres;
|
|
|
|
if (SUCCEEDED(ParseLiteralToken(&m_pszCurrent, szLeftParen, NULL)))
|
|
{
|
|
hres = ParseExtensionData(pOpt);
|
|
if (FAILED(hres))
|
|
{
|
|
return hres;
|
|
}
|
|
|
|
return ParseLiteralToken(&m_pszCurrent, szRightParen, &m_pszInvalidString);
|
|
}
|
|
|
|
if (SUCCEEDED(GetQuotedToken(&m_pszCurrent, NULL)))
|
|
{
|
|
SkipWhitespace();
|
|
return NOERROR;
|
|
}
|
|
|
|
hres = ParseNumber(&m_pszCurrent, NULL);
|
|
if (FAILED(hres))
|
|
{
|
|
m_pszInvalidString = m_pszCurrent;
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/* ParseExtension parses an extension option. Syntax is:
|
|
*
|
|
* extension ( mandatory|optional "identifyingURL" data )
|
|
*
|
|
* Currently all extensions are parsed but ignored, although a mandatory
|
|
* extension causes the entire options structure and anything dependent
|
|
* on it to be invalidated.
|
|
*/
|
|
HRESULT CParsedServiceInfo::ParseExtension(COptionsBase *pOpt)
|
|
{
|
|
HRESULT hres;
|
|
|
|
hres = ParseLiteralToken(&m_pszCurrent, szLeftParen, &m_pszInvalidString);
|
|
if (FAILED(hres))
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseExtension() - Missing '(' at '%s'!", m_pszInvalidString );
|
|
return hres;
|
|
}
|
|
|
|
hres = ParseLiteralToken(&m_pszCurrent, szOptional, &m_pszInvalidString);
|
|
if (FAILED(hres))
|
|
{
|
|
hres = ParseLiteralToken(&m_pszCurrent, szMandatory, &m_pszInvalidString);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
pOpt->m_fdwFlags |= LBLOPT_INVALID;
|
|
}
|
|
}
|
|
|
|
if (FAILED(hres))
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseExtension() - Failed ParseLiteralToken() with hres=0x%x at '%s'!", hres, m_pszInvalidString );
|
|
return hres; /* this causes us to lose our place -- OK? */
|
|
}
|
|
|
|
hres = GetQuotedToken(&m_pszCurrent, NULL);
|
|
if (FAILED(hres))
|
|
{
|
|
m_pszInvalidString = m_pszCurrent;
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseExtension() - Missing Quote at '%s'!", m_pszInvalidString );
|
|
return hres;
|
|
}
|
|
|
|
SkipWhitespace();
|
|
|
|
while (*m_pszCurrent != ')' && *m_pszCurrent != '\0')
|
|
{
|
|
hres = ParseExtensionData(pOpt);
|
|
if (FAILED(hres))
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseExtension() - Failed ParseExtensionData() with hres=0x%x!", hres );
|
|
return hres;
|
|
}
|
|
}
|
|
|
|
if (*m_pszCurrent != ')')
|
|
{
|
|
m_pszInvalidString = m_pszCurrent;
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseExtension() - Missing ')' at '%s'!", m_pszInvalidString );
|
|
return ResultFromScode(MK_E_SYNTAX);
|
|
}
|
|
|
|
m_pszCurrent++;
|
|
SkipWhitespace();
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
/* ParseTime parses a "quoted-ISO-date" as found in a label. This is required
|
|
* to have the following form, as quoted from the PICS spec:
|
|
*
|
|
* quoted-ISO-date :: YYYY'.'MM'.'DD'T'hh':'mmStz
|
|
* YYYY :: four-digit year
|
|
* MM :: two-digit month (01=January, etc.)
|
|
* DD :: two-digit day of month (01-31)
|
|
* hh :: two digits of hour (00-23)
|
|
* mm :: two digits of minute (00-59)
|
|
* S :: sign of time zone offset from UTC (+ or -)
|
|
* tz :: four digit amount of offset from UTC (e.g., 1512 means 15 hours 12 minutes)
|
|
*
|
|
* Example: "1994.11.05T08:15-0500" means Nov. 5, 1994, 8:15am, US EST.
|
|
*
|
|
* Time is parsed into NET format -- seconds since 1970 (easiest to adjust for
|
|
* time zones, and compare with). Returns an error if string is invalid.
|
|
*/
|
|
|
|
/* Template describing the string format. 'n' means a digit, '+' means a
|
|
* plus or minus sign, any other character must match that literal character.
|
|
*/
|
|
const char szTimeTemplate[] = "nnnn.nn.nnTnn:nn+nnnn";
|
|
const char szPICSRulesTimeTemplate[] = "nnnn-nn-nnTnn:nn+nnnn";
|
|
|
|
HRESULT ParseTime(LPSTR pszTime, DWORD *pOut, BOOL fPICSRules)
|
|
{
|
|
/* Copy the time string into a temporary buffer, since we're going to
|
|
* stomp on some separators. We preserve the original in case it turns
|
|
* out to be invalid and we have to show it to the user later.
|
|
*/
|
|
LPCSTR pszCurrTemplate;
|
|
|
|
char szTemp[sizeof(szTimeTemplate)];
|
|
|
|
if (::strlenf(pszTime) >= sizeof(szTemp))
|
|
{
|
|
TraceMsg( TF_WARNING, "ParseTime() - Time String Too Long (pszTime='%s', %d chars expected)!", pszTime, sizeof(szTemp) );
|
|
return ResultFromScode(MK_E_SYNTAX);
|
|
}
|
|
|
|
strcpyf(szTemp, pszTime);
|
|
|
|
LPSTR pszCurrent = szTemp;
|
|
|
|
if(fPICSRules)
|
|
{
|
|
pszCurrTemplate = szPICSRulesTimeTemplate;
|
|
}
|
|
else
|
|
{
|
|
pszCurrTemplate = szTimeTemplate;
|
|
}
|
|
|
|
/* First validate the format against the template. If that succeeds, then
|
|
* we get to make all sorts of assumptions later.
|
|
*
|
|
* We stomp all separators except the +/- for the timezone with spaces
|
|
* so that ParseNumber will (a) skip them for us, and (b) not interpret
|
|
* the '.' separators as decimal points.
|
|
*/
|
|
BOOL fOK = TRUE;
|
|
while (*pszCurrent && *pszCurrTemplate && fOK)
|
|
{
|
|
char chCurrent = *pszCurrent;
|
|
|
|
switch (*pszCurrTemplate)
|
|
{
|
|
case 'n':
|
|
if (chCurrent < '0' || chCurrent > '9')
|
|
{
|
|
fOK = FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
case '+':
|
|
if (chCurrent != '+' && chCurrent != '-')
|
|
{
|
|
fOK = FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
if (chCurrent != *pszCurrTemplate)
|
|
{
|
|
fOK = FALSE;
|
|
}
|
|
else
|
|
{
|
|
*pszCurrent = ' ';
|
|
}
|
|
break;
|
|
}
|
|
|
|
pszCurrent++;
|
|
pszCurrTemplate++;
|
|
}
|
|
|
|
/* If invalid character, or didn't reach the ends of both strings
|
|
* simultaneously, fail.
|
|
*/
|
|
if (!fOK || *pszCurrent || *pszCurrTemplate)
|
|
{
|
|
TraceMsg( TF_WARNING, "ParseTime() - Invalid Character or Strings Mismatch (fOK=%d, pszCurrent='%s', pszCurrTemplate='%s')!", fOK, pszCurrent, pszCurrTemplate );
|
|
return ResultFromScode(MK_E_SYNTAX);
|
|
}
|
|
|
|
HRESULT hres;
|
|
int n;
|
|
SYSTEMTIME st;
|
|
|
|
/* We parse into SYSTEMTIME structure because it has separate fields for
|
|
* the different components. We then convert to net time (seconds since
|
|
* Jan 1 1970) to easily add the timezone bias and compare with other
|
|
* times.
|
|
*
|
|
* The sense of the bias sign is inverted because it indicates the direction
|
|
* of the bias FROM UTC. We want to use it to convert the specified time
|
|
* back TO UTC.
|
|
*/
|
|
|
|
int nBiasSign = -1;
|
|
int nBiasNumber;
|
|
pszCurrent = szTemp;
|
|
hres = ParseNumber(&pszCurrent, &n);
|
|
if (SUCCEEDED(hres) && n >= 1980)
|
|
{
|
|
st.wYear = (WORD)n;
|
|
hres = ParseNumber(&pszCurrent, &n);
|
|
if (SUCCEEDED(hres) && n <= 12)
|
|
{
|
|
st.wMonth = (WORD)n;
|
|
hres = ParseNumber(&pszCurrent, &n);
|
|
if (SUCCEEDED(hres) && n < 32)
|
|
{
|
|
st.wDay = (WORD)n;
|
|
hres = ParseNumber(&pszCurrent, &n);
|
|
if (SUCCEEDED(hres) && n <= 23)
|
|
{
|
|
st.wHour = (WORD)n;
|
|
hres = ParseNumber(&pszCurrent, &n);
|
|
if (SUCCEEDED(hres) && n <= 59)
|
|
{
|
|
st.wMinute = (WORD)n;
|
|
if (*(pszCurrent++) == '-')
|
|
{
|
|
nBiasSign = 1;
|
|
}
|
|
|
|
hres = ParseNumber(&pszCurrent, &nBiasNumber);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Seconds are used by the time converter, but are not specified in
|
|
* the label.
|
|
*/
|
|
st.wSecond = 0;
|
|
|
|
/* Other fields (wDayOfWeek, wMilliseconds) are ignored when converting
|
|
* to net time.
|
|
*/
|
|
|
|
if (FAILED(hres))
|
|
{
|
|
TraceMsg( TF_WARNING, "ParseTime() - Failed to Parse Time where hres=0x%x!", hres );
|
|
return hres;
|
|
}
|
|
|
|
DWORD dwTime = SystemToNetDate(&st);
|
|
|
|
/* The bias number is 4 digits, but hours and minutes. Convert to
|
|
* a number of seconds.
|
|
*/
|
|
nBiasNumber = (((nBiasNumber / 100) * 60) + (nBiasNumber % 100)) * 60;
|
|
|
|
/* Adjust the time by the timezone bias, and return to the caller. */
|
|
*pOut = dwTime + (nBiasNumber * nBiasSign);
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/* ParseOptions parses through any label options that may be present at
|
|
* m_pszCurrent. pszTokenEnd initially points to the end of the token at
|
|
* m_pszCurrent, a small perf win since the caller has already calculated
|
|
* it. If ParseOptions is filling in the static options structure embedded
|
|
* in the serviceinfo, pOpt points to it and ppOptOut will be NULL. If pOpt
|
|
* is NULL, then ParseOptions will construct a new CDynamicOptions object
|
|
* and return it in *ppOptOut, iff any new options are found at the current
|
|
* token. pszOptionEndToken indicates the token which ends the list of
|
|
* options -- either "labels" or "ratings". A token consisting of just the
|
|
* first character of pszOptionEndToken will also terminate the list.
|
|
*
|
|
* ParseOptions fails iff it finds an option it doesn't recognize, or a
|
|
* syntax error in an option it does recognize. It succeeds if all options
|
|
* are syntactically correct or if there are no options to parse.
|
|
*
|
|
* The token which terminates the list of options is also consumed.
|
|
*
|
|
* FEATURE - how should we flag mandatory extensions, 'until' options that
|
|
* give an expired date, etc.? set a flag in the CParsedServiceInfo and
|
|
* keep parsing?
|
|
*/
|
|
|
|
enum OptionID {
|
|
OID_AT,
|
|
OID_BY,
|
|
OID_COMMENT,
|
|
OID_FULL,
|
|
OID_EXTENSION,
|
|
OID_GENERIC,
|
|
OID_FOR,
|
|
OID_MIC,
|
|
OID_ON,
|
|
OID_SIG,
|
|
OID_UNTIL
|
|
};
|
|
|
|
enum OptionContents {
|
|
OC_QUOTED,
|
|
OC_BOOL,
|
|
OC_SPECIAL
|
|
};
|
|
|
|
const struct {
|
|
LPCSTR pszToken;
|
|
OptionID oid;
|
|
OptionContents oc;
|
|
} aKnownOptions[] = {
|
|
{ szAtOption, OID_AT, OC_QUOTED },
|
|
{ szByOption, OID_BY, OC_QUOTED },
|
|
{ szCommentOption, OID_COMMENT, OC_QUOTED },
|
|
{ szCompleteLabelOption, OID_FULL, OC_QUOTED },
|
|
{ szFullOption, OID_FULL, OC_QUOTED },
|
|
{ szExtensionOption, OID_EXTENSION, OC_SPECIAL },
|
|
{ szGenericOption, OID_GENERIC, OC_BOOL },
|
|
{ szShortGenericOption, OID_GENERIC, OC_BOOL },
|
|
{ szForOption, OID_FOR, OC_QUOTED },
|
|
{ szMICOption, OID_MIC, OC_QUOTED },
|
|
{ szMD5Option, OID_MIC, OC_QUOTED },
|
|
{ szOnOption, OID_ON, OC_QUOTED },
|
|
{ szSigOption, OID_SIG, OC_QUOTED },
|
|
{ szUntilOption, OID_UNTIL, OC_QUOTED },
|
|
{ szExpOption, OID_UNTIL, OC_QUOTED }
|
|
};
|
|
|
|
const UINT cKnownOptions = sizeof(aKnownOptions) / sizeof(aKnownOptions[0]);
|
|
|
|
|
|
HRESULT CParsedServiceInfo::ParseOptions(LPSTR pszTokenEnd, COptionsBase *pOpt,
|
|
CDynamicOptions **ppOptOut, LPCSTR pszOptionEndToken)
|
|
{
|
|
HRESULT hres = NOERROR;
|
|
char szShortOptionEndToken[2];
|
|
|
|
szShortOptionEndToken[0] = *pszOptionEndToken;
|
|
szShortOptionEndToken[1] = '\0';
|
|
|
|
if (pszTokenEnd == NULL)
|
|
{
|
|
pszTokenEnd = FindTokenEnd(m_pszCurrent);
|
|
}
|
|
|
|
do
|
|
{
|
|
/* Have we hit the token that signals the end of the options? */
|
|
if (IsEqualToken(m_pszCurrent, pszTokenEnd, pszOptionEndToken) ||
|
|
IsEqualToken(m_pszCurrent, pszTokenEnd, szShortOptionEndToken))
|
|
{
|
|
m_pszCurrent = pszTokenEnd;
|
|
SkipWhitespace();
|
|
return NOERROR;
|
|
}
|
|
|
|
for (UINT i=0; i<cKnownOptions; i++)
|
|
{
|
|
if (IsEqualToken(m_pszCurrent, pszTokenEnd, aKnownOptions[i].pszToken))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == cKnownOptions)
|
|
{
|
|
m_pszInvalidString = m_pszCurrent;
|
|
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseOptions() - Unknown Token Encountered at '%s'!", m_pszInvalidString );
|
|
|
|
return ResultFromScode(MK_E_SYNTAX); /* unrecognized option */
|
|
}
|
|
|
|
m_pszCurrent = pszTokenEnd;
|
|
SkipWhitespace();
|
|
|
|
/* Now parse the stuff that comes after the option token. */
|
|
LPSTR pszQuotedString = NULL;
|
|
BOOL fBoolOpt = FALSE;
|
|
switch (aKnownOptions[i].oc)
|
|
{
|
|
case OC_QUOTED:
|
|
hres = GetQuotedToken(&m_pszCurrent, &pszQuotedString);
|
|
break;
|
|
|
|
case OC_BOOL:
|
|
hres = GetBool(&m_pszCurrent, &fBoolOpt);
|
|
break;
|
|
|
|
case OC_SPECIAL:
|
|
break; /* we'll handle this specially */
|
|
}
|
|
|
|
if (FAILED(hres))
|
|
{ /* incorrect stuff after the option token */
|
|
m_pszInvalidString = m_pszCurrent;
|
|
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseOptions() - Failed Option Contents Parse at '%s'!", m_pszInvalidString );
|
|
|
|
return hres;
|
|
}
|
|
|
|
if (pOpt == NULL)
|
|
{ /* need to allocate a new options structure */
|
|
CDynamicOptions *pNew = new CDynamicOptions;
|
|
if (pNew == NULL)
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseOptions() - Failed to Create CDynamicOptions Object!" );
|
|
return ResultFromScode(E_OUTOFMEMORY);
|
|
}
|
|
|
|
pOpt = pNew;
|
|
*ppOptOut = pNew; /* return new structure to caller */
|
|
}
|
|
|
|
/* Now actually do useful stuff based on which option it is. */
|
|
switch (aKnownOptions[i].oid)
|
|
{
|
|
case OID_UNTIL:
|
|
hres = ParseTime(pszQuotedString, &pOpt->m_timeUntil);
|
|
if (FAILED(hres))
|
|
{
|
|
m_pszInvalidString = pszQuotedString;
|
|
}
|
|
|
|
break;
|
|
|
|
case OID_FOR:
|
|
pOpt->m_pszURL = pszQuotedString;
|
|
break;
|
|
|
|
case OID_GENERIC:
|
|
if (fBoolOpt)
|
|
{
|
|
pOpt->m_fdwFlags |= LBLOPT_GENERIC;
|
|
}
|
|
else
|
|
{
|
|
pOpt->m_fdwFlags &= ~LBLOPT_GENERIC;
|
|
}
|
|
break;
|
|
|
|
case OID_EXTENSION:
|
|
hres = ParseExtension(pOpt);
|
|
break;
|
|
}
|
|
|
|
if ( FAILED(hres) )
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseOptions() - Failed Option ID Parse at '%s'!", m_pszCurrent );
|
|
}
|
|
|
|
SkipWhitespace();
|
|
|
|
pszTokenEnd = FindTokenEnd(m_pszCurrent);
|
|
} while (SUCCEEDED(hres));
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
/* CParsedServiceInfo::ParseRating parses a single rating -- a transmit-name
|
|
* followed by either a number or a parenthesized list of multi-values. The
|
|
* corresponding rating is stored in the current list of ratings.
|
|
*/
|
|
HRESULT CParsedServiceInfo::ParseRating()
|
|
{
|
|
LPSTR pszTokenEnd = FindTokenEnd(m_pszCurrent);
|
|
if (*m_pszCurrent == '\0')
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseRating() - Empty String after FindTokenEnd()!" );
|
|
return ResultFromScode(MK_E_SYNTAX);
|
|
}
|
|
|
|
*(pszTokenEnd++) = '\0';
|
|
|
|
CParsedRating r;
|
|
|
|
r.pszTransmitName = m_pszCurrent;
|
|
m_pszCurrent = pszTokenEnd;
|
|
SkipWhitespace();
|
|
|
|
HRESULT hres = ParseNumber(&m_pszCurrent, &r.nValue);
|
|
if (FAILED(hres))
|
|
{
|
|
m_pszInvalidString = m_pszCurrent;
|
|
return hres;
|
|
}
|
|
|
|
r.pOptions = m_poptCurrent;
|
|
r.fFound = FALSE;
|
|
r.fFailed = FALSE;
|
|
|
|
return (aRatings.Append(r) ? NOERROR : ResultFromScode(E_OUTOFMEMORY));
|
|
}
|
|
|
|
|
|
/* CParsedServiceInfo::ParseSingleLabel starts parsing where a single-label
|
|
* should occur. A single-label may contain options (in which case a new
|
|
* options structure will be allocated), following by the keyword 'ratings'
|
|
* (or 'r') and a parenthesized list of ratings.
|
|
*/
|
|
HRESULT CParsedServiceInfo::ParseSingleLabel()
|
|
{
|
|
HRESULT hres;
|
|
CDynamicOptions *pOpt = NULL;
|
|
|
|
hres = ParseOptions(NULL, NULL, &pOpt, szRatings);
|
|
if (FAILED(hres))
|
|
{
|
|
if (pOpt != NULL)
|
|
{
|
|
pOpt->Release();
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
if (pOpt != NULL)
|
|
{
|
|
pOpt->m_pNext = m_poptList;
|
|
m_poptList = pOpt;
|
|
m_poptCurrent = pOpt;
|
|
}
|
|
|
|
hres = ParseLiteralToken(&m_pszCurrent, szLeftParen, &m_pszInvalidString);
|
|
if (FAILED(hres))
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseSingleLabel() - ParseLiteralToken() Failed with hres=0x%x!", hres );
|
|
return hres;
|
|
}
|
|
|
|
do
|
|
{
|
|
hres = ParseRating();
|
|
} while (SUCCEEDED(hres) && *m_pszCurrent != ')' && *m_pszCurrent != '\0');
|
|
|
|
if (FAILED(hres))
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseSingleLabel() - ParseRating() Failed with hres=0x%x!", hres );
|
|
return hres;
|
|
}
|
|
|
|
return ParseLiteralToken(&m_pszCurrent, szRightParen, &m_pszInvalidString);
|
|
}
|
|
|
|
|
|
/* CParsedServiceInfo::ParseLabels starts parsing just past the keyword
|
|
* 'labels' (or 'l'). It needs to handle a label-error, a single-label,
|
|
* or a parenthesized list of single-labels.
|
|
*/
|
|
HRESULT CParsedServiceInfo::ParseLabels()
|
|
{
|
|
HRESULT hres;
|
|
|
|
/* First deal with a label-error. It begins with the keyword 'error'. */
|
|
if (SUCCEEDED(ParseLiteralToken(&m_pszCurrent, szError, NULL)))
|
|
{
|
|
hres = ParseLiteralToken(&m_pszCurrent, szLeftParen, &m_pszInvalidString);
|
|
if (FAILED(hres))
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseLabels() - ParseLiteralToken() Failed with hres=0x%x!", hres );
|
|
return hres;
|
|
}
|
|
|
|
LPSTR pszTokenEnd = FindTokenEnd(m_pszCurrent);
|
|
m_pszErrorString = m_pszCurrent;
|
|
m_pszCurrent = pszTokenEnd;
|
|
SkipWhitespace();
|
|
|
|
while (*m_pszCurrent != ')')
|
|
{
|
|
hres = GetQuotedToken(&m_pszCurrent, NULL);
|
|
if (FAILED(hres))
|
|
{
|
|
m_pszInvalidString = m_pszCurrent;
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseLabels() - GetQuotedToken() Failed with hres=0x%x!", hres );
|
|
return hres;
|
|
}
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
BOOL fParenthesized = FALSE;
|
|
|
|
/* If we see a left paren, it's a parenthesized list of single-labels,
|
|
* which basically means we'll have to eat an extra parenthesis later.
|
|
*/
|
|
if (SUCCEEDED(ParseLiteralToken(&m_pszCurrent, szLeftParen, NULL)))
|
|
{
|
|
fParenthesized = TRUE;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
/* Things which signify the end of the label list:
|
|
* - the close parenthesis checked for above
|
|
* - a quoted string, indicating the next service-info
|
|
* - the end of the string
|
|
* - a service-info saying "error (no-ratings <explanation>)"
|
|
*
|
|
* Check the easy ones first.
|
|
*/
|
|
if (*m_pszCurrent == ')' || *m_pszCurrent == '\"' || *m_pszCurrent == '\0')
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Now look for that tricky error-state service-info. */
|
|
LPSTR pszTemp = m_pszCurrent;
|
|
if (SUCCEEDED(ParseLiteralToken(&pszTemp, szError, NULL)) &&
|
|
SUCCEEDED(ParseLiteralToken(&pszTemp, szLeftParen, NULL)) &&
|
|
SUCCEEDED(ParseLiteralToken(&pszTemp, szNoRatings, NULL)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
hres = ParseSingleLabel();
|
|
if (FAILED(hres))
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedServiceInfo::ParseLabels() - ParseSingleLabel() Failed with hres=0x%x!", hres );
|
|
return hres;
|
|
}
|
|
}
|
|
|
|
if (fParenthesized)
|
|
{
|
|
return ParseLiteralToken(&m_pszCurrent, szRightParen, &m_pszInvalidString);
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
/* Parse is passed a pointer to a pointer to something which should
|
|
* be a service-info string (i.e., not the close paren for the labellist, and
|
|
* not the end of the string). The caller's string pointer is advanced to the
|
|
* end of the service-info string.
|
|
*/
|
|
HRESULT CParsedServiceInfo::Parse(LPSTR *ppszServiceInfo)
|
|
{
|
|
/* NOTE: Do not return out of this function without copying m_pszCurrent
|
|
* back into *ppszServiceInfo! Always store your return code in hres and
|
|
* exit out the bottom of the function.
|
|
*/
|
|
HRESULT hres;
|
|
|
|
m_pszCurrent = *ppszServiceInfo;
|
|
|
|
hres = ParseLiteralToken(&m_pszCurrent, szError, NULL);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
/* Keyword is 'error'. Better be followed by '(', 'no-ratings',
|
|
* explanations, and a close-paren.
|
|
*/
|
|
hres = ParseLiteralToken(&m_pszCurrent, szLeftParen, &m_pszInvalidString);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
hres = ParseLiteralToken(&m_pszCurrent, szNoRatings, &m_pszInvalidString);
|
|
}
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
m_pszErrorString = szNoRatings;
|
|
|
|
while (*m_pszCurrent != ')' && *m_pszCurrent != '\0')
|
|
{
|
|
hres = GetQuotedToken(&m_pszCurrent, NULL);
|
|
if (FAILED(hres))
|
|
{
|
|
m_pszInvalidString = m_pszCurrent;
|
|
break;
|
|
}
|
|
|
|
SkipWhitespace();
|
|
}
|
|
|
|
if (*m_pszCurrent == ')')
|
|
{
|
|
m_pszCurrent++;
|
|
SkipWhitespace();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Keyword is not 'error'. Better start with a serviceID --
|
|
* a quoted URL.
|
|
*/
|
|
LPSTR pszServiceID;
|
|
hres = GetQuotedToken(&m_pszCurrent, &pszServiceID);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
m_pszServiceName = pszServiceID;
|
|
|
|
SkipWhitespace();
|
|
|
|
/* Past the serviceID. Next either 'error' indicating a service-error,
|
|
* or we start options and then a labelword.
|
|
*/
|
|
|
|
LPSTR pszTokenEnd = FindTokenEnd(m_pszCurrent);
|
|
|
|
if (IsEqualToken(m_pszCurrent, pszTokenEnd, szError))
|
|
{
|
|
m_pszCurrent = pszTokenEnd;
|
|
SkipWhitespace();
|
|
hres = ParseServiceError();
|
|
}
|
|
else
|
|
{
|
|
hres = ParseOptions(pszTokenEnd, &m_opt, NULL, ::szLabelWord);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
hres = ParseLabels();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pszInvalidString = m_pszCurrent;
|
|
}
|
|
}
|
|
|
|
*ppszServiceInfo = m_pszCurrent;
|
|
return hres;
|
|
}
|
|
|
|
|
|
const char szPicsVersionLabel[] = "PICS-";
|
|
const UINT cchLabel = (sizeof(szPicsVersionLabel)-1) / sizeof(szPicsVersionLabel[0]);
|
|
|
|
HRESULT CParsedLabelList::Parse(LPSTR pszCopy)
|
|
{
|
|
m_pszList = pszCopy; /* we own the label list string now */
|
|
|
|
/* Make another copy, which we won't carve up during parsing, so that the
|
|
* access-denied dialog can compare literal labels.
|
|
*/
|
|
m_pszOriginalLabel = new char[::strlenf(pszCopy)+1];
|
|
if (m_pszOriginalLabel != NULL)
|
|
{
|
|
::strcpyf(m_pszOriginalLabel, pszCopy);
|
|
}
|
|
|
|
m_pszCurrent = m_pszList;
|
|
|
|
SkipWhitespace();
|
|
|
|
HRESULT hres;
|
|
|
|
hres = ParseLiteralToken(&m_pszCurrent, szLeftParen, &m_pszInvalidString);
|
|
if (FAILED(hres))
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedLabelList::Parse() - ParseLiteralToken() Failed with hres=0x%x!", hres );
|
|
return hres;
|
|
}
|
|
|
|
if (strnicmpf(m_pszCurrent, szPicsVersionLabel, cchLabel))
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedLabelList::Parse() - Pics Version Label Comparison Failed at '%s'!", m_pszCurrent );
|
|
return ResultFromScode(MK_E_SYNTAX);
|
|
}
|
|
|
|
m_pszCurrent += cchLabel;
|
|
INT nVersion;
|
|
hres = ParseNumber(&m_pszCurrent, &nVersion);
|
|
if (FAILED(hres))
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedLabelList::Parse() - ParseNumber() Failed with hres=0x%x!", hres );
|
|
return hres;
|
|
}
|
|
|
|
CParsedServiceInfo *psi = &m_ServiceInfo;
|
|
|
|
do
|
|
{
|
|
hres = psi->Parse(&m_pszCurrent);
|
|
if (FAILED(hres))
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedLabelList::Parse() - psi->Parse() Failed with hres=0x%x!", hres );
|
|
return hres;
|
|
}
|
|
|
|
if (*m_pszCurrent != ')' && *m_pszCurrent != '\0')
|
|
{
|
|
CParsedServiceInfo *pNew = new CParsedServiceInfo;
|
|
if (pNew == NULL)
|
|
{
|
|
TraceMsg( TF_WARNING, "CParsedLabelList::Parse() - Failed to Create CParsedServiceInfo!" );
|
|
return ResultFromScode(E_OUTOFMEMORY);
|
|
}
|
|
|
|
psi->Append(pNew);
|
|
psi = pNew;
|
|
}
|
|
} while (*m_pszCurrent != ')' && *m_pszCurrent != '\0');
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
HRESULT ParseLabelList(LPCSTR pszList, CParsedLabelList **ppParsed)
|
|
{
|
|
LPSTR pszCopy = new char[strlenf(pszList)+1];
|
|
if (pszCopy == NULL)
|
|
{
|
|
TraceMsg( TF_WARNING, "ParseLabelList() - Failed to Create pszCopy!" );
|
|
return ResultFromScode(E_OUTOFMEMORY);
|
|
}
|
|
|
|
::strcpyf(pszCopy, pszList);
|
|
|
|
*ppParsed = new CParsedLabelList;
|
|
if (*ppParsed == NULL)
|
|
{
|
|
TraceMsg( TF_WARNING, "ParseLabelList() - Failed to Create CParsedLabelList!" );
|
|
delete pszCopy;
|
|
pszCopy = NULL;
|
|
return ResultFromScode(E_OUTOFMEMORY);
|
|
}
|
|
|
|
return (*ppParsed)->Parse(pszCopy);
|
|
}
|
|
|
|
|
|
void FreeParsedLabelList(CParsedLabelList *pList)
|
|
{
|
|
delete pList;
|
|
pList = NULL;
|
|
}
|