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