STDMETHODIMP VersionRange::ParseVersionRange()

in src/vswhere.lib/VersionRange.cpp [48:190]


STDMETHODIMP VersionRange::ParseVersionRange(
    _In_ LPCOLESTR pwszVersionRange,
    _Out_ PULONGLONG pullMinVersion,
    _Out_ PULONGLONG pullMaxVersion
)
{
    HRESULT hr = S_OK;
    ULONGLONG ullMinVersion = MinVersion;
    ULONGLONG ullMaxVersion = MaxVersion;
    LPCWSTR pwszVersionStart = NULL;
    LPCWSTR pwszVersionEnd = NULL;
    bool sep = false;
    bool single = false;
    bool isMinInclusive = true;
    bool isMaxInclusive = true;

    ExitOnNull(pwszVersionRange, hr, E_INVALIDARG, "Missing required parameter: %hs", _nameof(pwszVersionRange));
    ExitOnNull(pullMinVersion, hr, E_POINTER, "Output parameter is NULL.");
    ExitOnNull(pullMaxVersion, hr, E_POINTER, "Output parameter is NULL.");

    *pullMinVersion = 0;
    *pullMaxVersion = 0;

    LPWSTR pwszVersionRangeEnd = NULL;
    pwszVersionRange = Trim(pwszVersionRange, &pwszVersionRangeEnd);

    if (pwszVersionRange > pwszVersionRangeEnd)
    {
        // Should not be possible but check anyway.
        ExitOnFailure(hr = E_UNEXPECTED, "Invalid string.");
    }

    const size_t cchVersionRange = pwszVersionRangeEnd - pwszVersionRange;
    ExitOnNull(cchVersionRange, hr, E_INVALIDARG, "Version range required.");

    for (size_t i = 0; i < cchVersionRange; ++i)
    {
        const auto pwsz = &pwszVersionRange[i];
        const auto ch = *pwsz;

        if (L'(' == ch || L'[' == ch)
        {
            if (0 != i)
            {
                ExitOnFailure(hr = E_INVALIDARG, "Invalid format.");
            }
            else if (L'(' == ch)
            {
                isMinInclusive = false;
            }
        }
        else if (L')' == ch || L']' == ch)
        {
            if (cchVersionRange - 1 != i)
            {
                ExitOnFailure(hr = E_INVALIDARG, "Invalid format.");
            }
            else if (L')' == ch)
            {
                isMaxInclusive = false;
            }

            // Only a single version if the separator was not yet parsed.
            single = !sep;
        }
        else if (L',' == ch)
        {
            if (sep)
            {
                // Already parsed a separator.
                ExitOnFailure(hr = E_INVALIDARG, "Invalid format.");
            }

            sep = true;

            if (pwszVersionEnd > pwszVersionStart)
            {
                hr = ParseVersionString(pwszVersionStart, pwszVersionEnd, &ullMinVersion);
                ExitOnFailure(hr, "Failed to parse version.");
            }

            pwszVersionStart = NULL;
            pwszVersionEnd = NULL;
        }
        else if (isspace(ch, neutral_locale))
        {
            continue;
        }
        else if (!pwszVersionStart)
        {
            pwszVersionStart = pwsz;
            pwszVersionEnd = pwsz + 1;
        }
        else
        {
            pwszVersionEnd = pwsz + 1;
        }
    }

    if (pwszVersionEnd > pwszVersionStart)
    {
        auto pullVersion = sep ? &ullMaxVersion : &ullMinVersion;

        hr = ParseVersionString(pwszVersionStart, pwszVersionEnd, pullVersion);
        ExitOnFailure(hr, "Failed to parse version.");

        if (single)
        {
            ullMaxVersion = ullMinVersion;
        }
    }

    if (!isMinInclusive)
    {
        if (_UI64_MAX == ullMinVersion)
        {
            ExitOnFailure(hr = E_INVALIDARG, "Arithmetic overflow.");
        }

        ullMinVersion++;
    }

    if (!isMaxInclusive)
    {
        if (0 == ullMaxVersion)
        {
            ExitOnFailure(hr = E_INVALIDARG, "Arithmetic underflow.");
        }

        ullMaxVersion--;
    }

    if (ullMinVersion > ullMaxVersion)
    {
        ExitOnFailure(hr = E_INVALIDARG, "Min greater than max.");
    }

    *pullMinVersion = ullMinVersion;
    *pullMaxVersion = ullMaxVersion;

Exit:
    return hr;
}