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 }