|
|
#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; }
|