winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

Versions.cpp (22557B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "Public/AppInstallerVersions.h"
      5 #include "Public/AppInstallerStrings.h"
      6 
      7 namespace AppInstaller::Utility
      8 {
      9     using namespace std::string_view_literals;
     10 
     11     static constexpr std::string_view s_Digit_Characters = "0123456789"sv;
     12     static constexpr std::string_view s_Version_Part_Latest = "Latest"sv;
     13     static constexpr std::string_view s_Version_Part_Unknown = "Unknown"sv;
     14 
     15     static constexpr std::string_view s_Approximate_Less_Than = "< "sv;
     16     static constexpr std::string_view s_Approximate_Greater_Than = "> "sv;
     17 
     18     Version::Version(std::string&& version, std::string_view splitChars)
     19     {
     20         Assign(std::move(version), splitChars);
     21     }
     22 
     23     RawVersion::RawVersion(std::string version, std::string_view splitChars)
     24     {
     25         m_trimPrefix = false;
     26         Assign(std::move(version), splitChars);
     27     }
     28 
     29     Version::Version(Version baseVersion, ApproximateComparator approximateComparator) : Version(std::move(baseVersion))
     30     {
     31         if (approximateComparator == ApproximateComparator::None)
     32         {
     33             return;
     34         }
     35 
     36         THROW_HR_IF(E_INVALIDARG, this->IsApproximate() || this->IsUnknown());
     37 
     38         m_approximateComparator = approximateComparator;
     39         if (approximateComparator == ApproximateComparator::LessThan)
     40         {
     41             m_version = std::string{ s_Approximate_Less_Than } + m_version;
     42         }
     43         else if (approximateComparator == ApproximateComparator::GreaterThan)
     44         {
     45             m_version = std::string{ s_Approximate_Greater_Than } + m_version;
     46         }
     47     }
     48 
     49     void Version::Assign(std::string version, std::string_view splitChars)
     50     {
     51         m_version = std::move(Utility::Trim(version));
     52 
     53         // Process approximate comparator if applicable
     54         std::string baseVersion = m_version;
     55         if (CaseInsensitiveStartsWith(m_version, s_Approximate_Less_Than))
     56         {
     57             m_approximateComparator = ApproximateComparator::LessThan;
     58             baseVersion = m_version.substr(s_Approximate_Less_Than.length(), m_version.length() - s_Approximate_Less_Than.length());
     59         }
     60         else if (CaseInsensitiveStartsWith(m_version, s_Approximate_Greater_Than))
     61         {
     62             m_approximateComparator = ApproximateComparator::GreaterThan;
     63             baseVersion = m_version.substr(s_Approximate_Greater_Than.length(), m_version.length() - s_Approximate_Greater_Than.length());
     64         }
     65 
     66         // If there is a digit before the split character, or no split characters exist, trim off all leading non-digit characters
     67         size_t digitPos = baseVersion.find_first_of(s_Digit_Characters);
     68         size_t splitPos = baseVersion.find_first_of(splitChars);
     69         if (m_trimPrefix && digitPos != std::string::npos && (splitPos == std::string::npos || digitPos < splitPos))
     70         {
     71             baseVersion.erase(0, digitPos);
     72         }
     73 
     74         // Then parse the base version
     75         size_t pos = 0;
     76 
     77         while (pos < baseVersion.length())
     78         {
     79             size_t newPos = baseVersion.find_first_of(splitChars, pos);
     80 
     81             size_t length = (newPos == std::string::npos ? baseVersion.length() : newPos) - pos;
     82             m_parts.emplace_back(baseVersion.substr(pos, length));
     83 
     84             pos += length + 1;
     85         }
     86 
     87         // Trim version parts
     88         Trim();
     89 
     90         THROW_HR_IF(E_INVALIDARG, m_approximateComparator != ApproximateComparator::None && IsBaseVersionUnknown());
     91     }
     92 
     93     void Version::Trim()
     94     {
     95         while (!m_parts.empty())
     96         {
     97             const Part& part = m_parts.back();
     98             if (part.Integer == 0 && part.Other.empty())
     99             {
    100                 m_parts.pop_back();
    101             }
    102             else
    103             {
    104                 return;
    105             }
    106         }
    107     }
    108 
    109     bool Version::operator<(const Version& other) const
    110     {
    111         // Sort Latest higher than any other values
    112         bool thisIsLatest = IsBaseVersionLatest();
    113         bool otherIsLatest = other.IsBaseVersionLatest();
    114 
    115         if (thisIsLatest && otherIsLatest)
    116         {
    117             return ApproximateCompareLessThan(other);
    118         }
    119         else if (thisIsLatest || otherIsLatest)
    120         {
    121             // If only one is latest, this can only be less than if the other is and this is not.
    122             return (otherIsLatest && !thisIsLatest);
    123         }
    124 
    125         // Sort Unknown lower than any known values
    126         bool thisIsUnknown = IsBaseVersionUnknown();
    127         bool otherIsUnknown = other.IsBaseVersionUnknown();
    128 
    129         if (thisIsUnknown && otherIsUnknown)
    130         {
    131             // This code path should always return false as we disable approximate version for Unknown for now
    132             return ApproximateCompareLessThan(other);
    133         }
    134         else if (thisIsUnknown || otherIsUnknown)
    135         {
    136             // If at least one is unknown, this can only be less than if it is and the other is not.
    137             return (thisIsUnknown && !otherIsUnknown);
    138         }
    139 
    140         const Part emptyPart{};
    141         for (size_t i = 0; i < std::max(m_parts.size(), other.m_parts.size()); ++i)
    142         {
    143             // Whichever version is shorter, we need to pad it with empty parts
    144             const Part& partA = (i >= m_parts.size()) ? emptyPart : m_parts[i];
    145             const Part& partB = (i >= other.m_parts.size()) ? emptyPart : other.m_parts[i];
    146 
    147             if (partA < partB)
    148             {
    149                 return true;
    150             }
    151             else if (partB < partA)
    152             {
    153                 return false;
    154             }
    155             // else parts are equal, so continue to next part
    156         }
    157 
    158         // All parts were compared and found to be equal
    159         return ApproximateCompareLessThan(other);
    160     }
    161 
    162     bool Version::operator>(const Version& other) const
    163     {
    164         return other < *this;
    165     }
    166 
    167     bool Version::operator<=(const Version& other) const
    168     {
    169         return !(*this > other);
    170     }
    171 
    172     bool Version::operator>=(const Version& other) const
    173     {
    174         return !(*this < other);
    175     }
    176 
    177     bool Version::operator==(const Version& other) const
    178     {
    179         if (m_approximateComparator != other.m_approximateComparator)
    180         {
    181             return false;
    182         }
    183 
    184         if ((IsBaseVersionLatest() && other.IsBaseVersionLatest()) ||
    185             (IsBaseVersionUnknown() && other.IsBaseVersionUnknown()))
    186         {
    187             return true;
    188         }
    189 
    190         if (m_parts.size() != other.m_parts.size())
    191         {
    192             return false;
    193         }
    194 
    195         for (size_t i = 0; i < m_parts.size(); ++i)
    196         {
    197             if (m_parts[i] != other.m_parts[i])
    198             {
    199                 return false;
    200             }
    201         }
    202 
    203         return true;
    204     }
    205 
    206     bool Version::operator!=(const Version& other) const
    207     {
    208         return !(*this == other);
    209     }
    210 
    211     bool Version::IsLatest() const
    212     {
    213         return (m_approximateComparator != ApproximateComparator::LessThan && IsBaseVersionLatest());
    214     }
    215 
    216     Version Version::CreateLatest()
    217     {
    218         Version result;
    219         result.m_version = s_Version_Part_Latest;
    220         result.m_parts.emplace_back(0, std::string{ s_Version_Part_Latest });
    221         return result;
    222     }
    223 
    224     bool Version::IsUnknown() const
    225     {
    226         return IsBaseVersionUnknown();
    227     }
    228 
    229     Version Version::CreateUnknown()
    230     {
    231         Version result;
    232         result.m_version = s_Version_Part_Unknown;
    233         result.m_parts.emplace_back(0, std::string{ s_Version_Part_Unknown });
    234         return result;
    235     }
    236 
    237     const Version::Part& Version::PartAt(size_t index) const
    238     {
    239         static Part s_zero{};
    240 
    241         if (index < m_parts.size())
    242         {
    243             return m_parts[index];
    244         }
    245         else
    246         {
    247             return s_zero;
    248         }
    249     }
    250 
    251     Version Version::GetBaseVersion() const
    252     {
    253         Version baseVersion = *this;
    254         baseVersion.m_approximateComparator = ApproximateComparator::None;
    255         if (m_approximateComparator == ApproximateComparator::LessThan)
    256         {
    257             baseVersion.m_version = m_version.substr(s_Approximate_Less_Than.size());
    258         }
    259         else if (m_approximateComparator == ApproximateComparator::GreaterThan)
    260         {
    261             baseVersion.m_version = m_version.substr(s_Approximate_Greater_Than.size());
    262         }
    263         
    264         return baseVersion;
    265     }
    266     
    267     bool Version::IsBaseVersionLatest() const
    268     {
    269         return (m_parts.size() == 1 && m_parts[0].Integer == 0 && Utility::CaseInsensitiveEquals(m_parts[0].Other, s_Version_Part_Latest));
    270     }
    271 
    272     bool Version::IsBaseVersionUnknown() const
    273     {
    274         return (m_parts.size() == 1 && m_parts[0].Integer == 0 && Utility::CaseInsensitiveEquals(m_parts[0].Other, s_Version_Part_Unknown));
    275     }
    276 
    277     bool Version::ApproximateCompareLessThan(const Version& other) const
    278     {
    279         // Only true if this is less than, other is not, OR this is none, other is greater than
    280         return (m_approximateComparator == ApproximateComparator::LessThan && other.m_approximateComparator != ApproximateComparator::LessThan) ||
    281             (m_approximateComparator == ApproximateComparator::None && other.m_approximateComparator == ApproximateComparator::GreaterThan);
    282     }
    283 
    284     Version::Part::Part(const std::string& part)
    285     {
    286         std::string interimPart = Utility::Trim(part.c_str());
    287         const char* begin = interimPart.c_str();
    288         char* end = nullptr;
    289         errno = 0;
    290         Integer = strtoull(begin, &end, 10);
    291 
    292         if (errno == ERANGE)
    293         {
    294             Integer = 0;
    295             Other = interimPart;
    296         }
    297         else if (static_cast<size_t>(end - begin) != interimPart.length())
    298         {
    299             Other = end;
    300         }
    301 
    302         m_foldedOther = Utility::FoldCase(static_cast<std::string_view>(Other));
    303     }
    304 
    305     Version::Part::Part(uint64_t integer, std::string other) :
    306         Integer(integer), Other(std::move(Utility::Trim(other)))
    307     {
    308         m_foldedOther = Utility::FoldCase(static_cast<std::string_view>(Other));
    309     }
    310 
    311     bool Version::Part::operator<(const Part& other) const
    312     {
    313         if (Integer < other.Integer)
    314         {
    315             return true;
    316         }
    317         else if (Integer > other.Integer)
    318         {
    319             return false;
    320         }
    321         else if (Other.empty())
    322         {
    323             // If this Other is empty, it is at least >=
    324             return false;
    325         }
    326         else if (!Other.empty() && other.Other.empty())
    327         {
    328             // If the other Other is empty and this is not, this is less.
    329             return true;
    330         }
    331         else if (m_foldedOther < other.m_foldedOther)
    332         {
    333             // Compare the folded versions
    334             return true;
    335         }
    336 
    337         // else Other >= other.Other
    338         return false;
    339     }
    340 
    341     bool Version::Part::operator==(const Part& other) const
    342     {
    343         return Integer == other.Integer && m_foldedOther == other.m_foldedOther;
    344     }
    345 
    346     bool Version::Part::operator!=(const Part& other) const
    347     {
    348         return !(*this == other);
    349     }
    350 
    351     bool Channel::operator<(const Channel& other) const
    352     {
    353         return m_channel < other.m_channel;
    354     }
    355 
    356     VersionAndChannel::VersionAndChannel(Version&& version, Channel&& channel) : 
    357         m_version(std::move(version)), m_channel(std::move(channel)) {}
    358 
    359     std::string VersionAndChannel::ToString() const
    360     {
    361         std::string result;
    362         result = m_version.ToString();
    363         if (!m_channel.ToString().empty())
    364         {
    365             result += '[';
    366             result += m_channel.ToString();
    367             result += ']';
    368         }
    369         return result;
    370     }
    371 
    372     bool VersionAndChannel::operator<(const VersionAndChannel& other) const
    373     {
    374         if (m_channel < other.m_channel)
    375         {
    376             return true;
    377         }
    378         else if (other.m_channel < m_channel)
    379         {
    380             return false;
    381         }
    382         // We intentionally invert the order for version here.
    383         else if (other.m_version < m_version)
    384         {
    385             return true;
    386         }
    387 
    388         // else m_version >= other.m_version
    389         return false;
    390     }
    391 
    392     bool VersionAndChannel::IsUpdatedBy(const VersionAndChannel& other) const
    393     {
    394         // Channel crossing should not happen here.
    395         if (!Utility::ICUCaseInsensitiveEquals(m_channel.ToString(), other.m_channel.ToString()))
    396         {
    397             return false;
    398         }
    399 
    400         return m_version < other.m_version;
    401     }
    402 
    403     UInt64Version::UInt64Version(UINT64 version)
    404     {
    405         Assign(version);
    406     }
    407 
    408     UInt64Version::UInt64Version(uint16_t major, uint16_t minor, uint16_t build, uint16_t revision)
    409     {
    410         Assign(major, minor, build, revision);
    411     }
    412 
    413     void UInt64Version::Assign(UINT64 version)
    414     {
    415         constexpr UINT64 mask16 = (1 << 16) - 1;
    416         uint16_t revision = version & mask16;
    417         uint16_t build = (version >> 0x10) & mask16;
    418         uint16_t minor = (version >> 0x20) & mask16;
    419         uint16_t major = (version >> 0x30) & mask16;
    420 
    421         Assign(major, minor, build, revision);
    422     }
    423 
    424     void UInt64Version::Assign(uint16_t major, uint16_t minor, uint16_t build, uint16_t revision)
    425     {
    426         // Construct a string representation of the provided version
    427         std::stringstream ssVersion;
    428         ssVersion << major
    429             << Version::DefaultSplitChars << minor
    430             << Version::DefaultSplitChars << build
    431             << Version::DefaultSplitChars << revision;
    432         m_version = ssVersion.str();
    433 
    434         // Construct the 4 parts
    435         m_parts = { major, minor, build, revision };
    436 
    437         // Trim version parts
    438         Trim();
    439     }
    440 
    441     UInt64Version::UInt64Version(std::string&& version, std::string_view splitChars)
    442     {
    443         Assign(std::move(version), splitChars);
    444     }
    445 
    446     void UInt64Version::Assign(std::string version, std::string_view splitChars)
    447     {
    448         Version::Assign(std::move(version), splitChars);
    449 
    450         // After trimming trailing parts (0 or empty),
    451         // at most 4 parts must be present
    452         THROW_HR_IF(E_INVALIDARG, m_parts.size() > 4);
    453         for (const auto& part : m_parts)
    454         {
    455             // Check for non-empty Other part
    456             THROW_HR_IF(E_INVALIDARG, !part.Other.empty());
    457 
    458             // Check for overflow Integer part
    459             THROW_HR_IF(E_INVALIDARG, part.Integer >> 16 != 0);
    460         }
    461     }
    462 
    463     SemanticVersion::SemanticVersion(std::string&& version)
    464     {
    465         Assign(std::move(version), DefaultSplitChars);
    466     }
    467 
    468     void SemanticVersion::Assign(std::string version, std::string_view splitChars)
    469     {
    470         // Semantic versions require using the default split character
    471         THROW_HR_IF(E_INVALIDARG, splitChars != DefaultSplitChars);
    472 
    473         // First split off any trailing build metadata
    474         std::string interimVersion = Utility::Trim(version);
    475         size_t buildMetadataPos = interimVersion.find('+', 0);
    476 
    477         if (buildMetadataPos != std::string::npos)
    478         {
    479             m_buildMetadata.Assign(interimVersion.substr(buildMetadataPos + 1));
    480             interimVersion.resize(buildMetadataPos);
    481         }
    482 
    483         // Now split off the prerelease data
    484         size_t prereleasePos = interimVersion.find('-', 0);
    485 
    486         if (prereleasePos != std::string::npos)
    487         {
    488             m_prerelease.Assign(interimVersion.substr(prereleasePos + 1));
    489             interimVersion.resize(prereleasePos);
    490         }
    491 
    492         // Parse main version
    493         Version::Assign(std::move(interimVersion), splitChars);
    494         THROW_HR_IF(E_INVALIDARG, IsApproximate());
    495         THROW_HR_IF(E_INVALIDARG, m_parts.size() > 3);
    496         for (size_t i = 0; i < 3; ++i)
    497         {
    498             THROW_HR_IF(E_INVALIDARG, !PartAt(i).Other.empty());
    499         }
    500 
    501         // Put rest of version back onto Other of last part
    502         size_t otherSplit = (prereleasePos != std::string::npos ? prereleasePos : buildMetadataPos);
    503         if (otherSplit != std::string::npos)
    504         {
    505             while (m_parts.size() < 3)
    506             {
    507                 m_parts.emplace_back();
    508             }
    509             m_parts[2].Other = version.substr(otherSplit);
    510         }
    511 
    512         // Overwrite the whole version string with our whole version string
    513         m_version = std::move(version);
    514     }
    515 
    516     bool SemanticVersion::IsPrerelease() const
    517     {
    518         return !m_prerelease.IsEmpty();
    519     }
    520 
    521     const Version& SemanticVersion::PrereleaseVersion() const
    522     {
    523         return m_prerelease;
    524     }
    525 
    526     bool SemanticVersion::HasBuildMetadata() const
    527     {
    528         return !m_buildMetadata.IsEmpty();
    529     }
    530 
    531     const Version& SemanticVersion::BuildMetadata() const
    532     {
    533         return m_buildMetadata;
    534     }
    535       
    536     VersionRange::VersionRange(Version first, Version second)
    537     {
    538         if (first < second)
    539         {
    540             m_minVersion = std::move(first);
    541             m_maxVersion = std::move(second);
    542         }
    543         else
    544         {
    545             m_minVersion = std::move(second);
    546             m_maxVersion = std::move(first);
    547         }
    548     }
    549 
    550     bool VersionRange::Overlaps(const VersionRange& other) const
    551     {
    552         // No overlap if either is an empty range.
    553         if (IsEmpty() || other.IsEmpty())
    554         {
    555             return false;
    556         }
    557 
    558         return m_minVersion <= other.m_maxVersion && m_maxVersion >= other.m_minVersion;
    559     }
    560 
    561     bool VersionRange::IsSameAsSingleVersion(const Version& version) const
    562     {
    563         if (IsEmpty())
    564         {
    565             return false;
    566         }
    567     
    568         return m_minVersion == version && m_maxVersion == version;
    569     }
    570 
    571     bool VersionRange::ContainsVersion(const Version& version) const
    572     {
    573         if (IsEmpty())
    574         {
    575             return false;
    576         }
    577 
    578         return version >= m_minVersion && version <= m_maxVersion;
    579     }
    580 
    581     bool VersionRange::operator<(const VersionRange& other) const
    582     {
    583         THROW_HR_IF(E_INVALIDARG, IsEmpty() || other.IsEmpty() || Overlaps(other));
    584         
    585         return m_minVersion < other.m_minVersion;
    586     }
    587 
    588     const Version& VersionRange::GetMinVersion() const
    589     {
    590         THROW_HR_IF(E_NOT_VALID_STATE, IsEmpty());
    591         return m_minVersion;
    592     }
    593 
    594     const Version& VersionRange::GetMaxVersion() const
    595     {
    596         THROW_HR_IF(E_NOT_VALID_STATE, IsEmpty());
    597         return m_maxVersion;
    598     }
    599 
    600     bool GatedVersion::IsValidVersion(Version version) const
    601     {
    602         auto gateParts = m_version.GetParts();
    603         if (gateParts.empty())
    604         {
    605             return false;
    606         }
    607 
    608         if (gateParts.back() != Version::Part("*"))
    609         {
    610             // Without wildcards, revert to direct comparison
    611             return m_version == version;
    612         }
    613 
    614         auto versionParts = version.GetParts();
    615         for (size_t i = 0; i < gateParts.size() - 1; ++i)
    616         {
    617             if (versionParts.size() > i)
    618             {
    619                 if (gateParts[i] == versionParts[i])
    620                 {
    621                     continue;
    622                 }
    623                 else
    624                 {
    625                     // Mismatch with the gated version
    626                     return false;
    627                 }
    628             }
    629             else
    630             {
    631                 // Assume trailing 0s on the version
    632                 if (gateParts[i] != Version::Part(0))
    633                 {
    634                     return false;
    635                 }
    636             }
    637         }
    638 
    639         // All version parts matched
    640         return true;
    641     }
    642 
    643     bool HasOverlapInVersionRanges(const std::vector<VersionRange>& ranges)
    644     {
    645         for (size_t i = 0; i < ranges.size(); i++)
    646         {
    647             for (size_t j = i + 1; j < ranges.size(); j++)
    648             {
    649                 if (ranges[i].Overlaps(ranges[j]))
    650                 {
    651                     return true;
    652                 }
    653             }
    654         }
    655 
    656         return false;
    657     }
    658 
    659     OpenTypeFontVersion::OpenTypeFontVersion(std::string&& version)
    660     {
    661         Assign(std::move(version), DefaultSplitChars);
    662     }
    663 
    664     void OpenTypeFontVersion::Assign(std::string version, std::string_view splitChars)
    665     {
    666         // Open type version requires using the default split character
    667         THROW_HR_IF(E_INVALIDARG, splitChars != DefaultSplitChars);
    668 
    669         // Split on default split character.
    670         std::vector<std::string> parts = Split(version, '.', true);
    671 
    672         std::string majorString;
    673         std::string minorString;
    674 
    675         // Font version must have a "major.minor" part.
    676         if (parts.size() >= 2)
    677         {
    678             // Find first digit and trim all preceding characters. 
    679             std::string firstPart = parts[0];
    680             size_t majorStartIndex = firstPart.find_first_of(s_Digit_Characters);
    681 
    682             if (majorStartIndex != std::string::npos)
    683             {
    684                 firstPart.erase(0, majorStartIndex);
    685             }
    686 
    687             size_t majorEndIndex = firstPart.find_last_of(s_Digit_Characters);
    688             majorString = firstPart.substr(0, majorEndIndex + 1);
    689 
    690             // Parse and verify minor part.
    691             std::string secondPart = parts[1];
    692             size_t endPos = secondPart.find_first_not_of(s_Digit_Characters);
    693 
    694             // If a non-digit character exists, trim off the remainder.
    695             if (endPos != std::string::npos)
    696             {
    697                 secondPart.erase(endPos, secondPart.length());
    698             }
    699 
    700             minorString = secondPart;
    701         }
    702 
    703         // Verify results.
    704         if (!majorString.empty() && !minorString.empty())
    705         {
    706             m_parts.emplace_back(majorString);
    707             m_parts.emplace_back(minorString);
    708             m_version = Utility::Join(DefaultSplitChars, { majorString, minorString });
    709 
    710             Trim();
    711         }
    712         else
    713         {
    714             m_version = s_Version_Part_Unknown;
    715             m_parts.emplace_back(0, std::string{ s_Version_Part_Unknown });
    716         }
    717     }
    718 }