PreIndexedPackageSourceFactory.cpp (32841B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Microsoft/PreIndexedPackageSourceFactory.h" 5 #include "Microsoft/SQLiteIndex.h" 6 #include "Microsoft/SQLiteIndexSource.h" 7 #include "SourceUpdateChecks.h" 8 9 #include <AppInstallerDateTime.h> 10 #include <AppInstallerDeployment.h> 11 #include <AppInstallerDownloader.h> 12 #include <AppInstallerMsixInfo.h> 13 #include <winget/ManagedFile.h> 14 #include <winget/ExperimentalFeature.h> 15 16 using namespace std::string_literals; 17 using namespace std::string_view_literals; 18 19 namespace AppInstaller::Repository::Microsoft 20 { 21 namespace 22 { 23 static constexpr std::string_view s_PreIndexedPackageSourceFactory_PackageFileName = "source.msix"sv; 24 static constexpr std::string_view s_PreIndexedPackageSourceFactory_V2_PackageFileName = "source2.msix"sv; 25 static constexpr std::string_view s_PreIndexedPackageSourceFactory_PackageVersionHeader = "x-ms-meta-sourceversion"sv; 26 static constexpr std::string_view s_PreIndexedPackageSourceFactory_IndexFileName = "index.db"sv; 27 // TODO: This being hard coded to force using the Public directory name is not ideal. 28 static constexpr std::string_view s_PreIndexedPackageSourceFactory_IndexFilePath = "Public\\index.db"sv; 29 30 // Construct the package location from the given details. 31 // Currently expects that the arg is an https uri pointing to the root of the data. 32 std::string GetPackageLocation(const std::string& basePath, std::string_view fileName) 33 { 34 std::string result = basePath; 35 if (result.back() != '/') 36 { 37 result += '/'; 38 } 39 result += fileName; 40 return result; 41 } 42 43 // Gets the set of package locations that should be tried, in order. 44 std::vector<std::string> GetPackageLocations(const SourceDetails& details) 45 { 46 THROW_HR_IF(E_INVALIDARG, details.Arg.empty()); 47 48 std::vector<std::string> result; 49 50 result.emplace_back(GetPackageLocation(details.Arg, s_PreIndexedPackageSourceFactory_V2_PackageFileName)); 51 result.emplace_back(GetPackageLocation(details.Arg, s_PreIndexedPackageSourceFactory_PackageFileName)); 52 53 if (!details.AlternateArg.empty()) 54 { 55 result.emplace_back(GetPackageLocation(details.AlternateArg, s_PreIndexedPackageSourceFactory_V2_PackageFileName)); 56 result.emplace_back(GetPackageLocation(details.AlternateArg, s_PreIndexedPackageSourceFactory_PackageFileName)); 57 } 58 59 return result; 60 } 61 62 // Abstracts the fallback for package location when the MsixInfo is needed. 63 struct PreIndexedPackageInfo 64 { 65 template <typename LocationCheck> 66 PreIndexedPackageInfo(const SourceDetails& details, LocationCheck&& locationCheck) 67 { 68 std::vector<std::string> potentialLocations = GetPackageLocations(details); 69 70 for (const auto& location : potentialLocations) 71 { 72 locationCheck(location); 73 } 74 75 std::exception_ptr primaryException; 76 77 for (const auto& location : potentialLocations) 78 { 79 try 80 { 81 m_msixInfo = std::make_unique<Msix::MsixInfo>(location); 82 m_packageLocation = location; 83 return; 84 } 85 catch (...) 86 { 87 LOG_CAUGHT_EXCEPTION_MSG("PreIndexedPackageInfo failed on location: %hs", location.c_str()); 88 if (!primaryException) 89 { 90 primaryException = std::current_exception(); 91 } 92 } 93 } 94 95 std::rethrow_exception(primaryException); 96 } 97 98 const std::string& PackageLocation() const { return m_packageLocation; } 99 Msix::MsixInfo& MsixInfo() { return *m_msixInfo; } 100 101 private: 102 std::string m_packageLocation; 103 std::unique_ptr<Msix::MsixInfo> m_msixInfo; 104 }; 105 106 // Abstracts the fallback for package location when an update is being done. 107 struct PreIndexedPackageUpdateCheck 108 { 109 PreIndexedPackageUpdateCheck(const SourceDetails& details) 110 { 111 std::vector<std::string> potentialLocations = GetPackageLocations(details); 112 113 std::exception_ptr primaryException; 114 115 for (const auto& location : potentialLocations) 116 { 117 try 118 { 119 m_availableVersion = GetAvailableVersionFrom(location); 120 m_packageLocation = location; 121 return; 122 } 123 catch (...) 124 { 125 LOG_CAUGHT_EXCEPTION_MSG("PreIndexedPackageUpdateCheck failed on location: %hs", location.c_str()); 126 if (!primaryException) 127 { 128 primaryException = std::current_exception(); 129 } 130 } 131 } 132 133 std::rethrow_exception(primaryException); 134 } 135 136 const std::string& PackageLocation() const { return m_packageLocation; } 137 const Msix::PackageVersion& AvailableVersion() const { return m_availableVersion; } 138 139 private: 140 std::string m_packageLocation; 141 Msix::PackageVersion m_availableVersion; 142 143 Msix::PackageVersion GetAvailableVersionFrom(const std::string& packageLocation) 144 { 145 if (Utility::IsUrlRemote(packageLocation)) 146 { 147 std::map<std::string, std::string> headers = Utility::GetHeaders(packageLocation); 148 auto itr = headers.find(std::string{ s_PreIndexedPackageSourceFactory_PackageVersionHeader }); 149 if (itr != headers.end()) 150 { 151 AICLI_LOG(Repo, Verbose, << "Header indicates version is: " << itr->second); 152 return { itr->second }; 153 } 154 155 // We did not find the header we were looking for, log the ones we did find 156 AICLI_LOG(Repo, Verbose, << "Did not find " << s_PreIndexedPackageSourceFactory_PackageVersionHeader << " in:\n" << [&]() 157 { 158 std::ostringstream headerLog; 159 for (const auto& header : headers) 160 { 161 headerLog << " " << header.first << " : " << header.second << '\n'; 162 } 163 return std::move(headerLog).str(); 164 }()); 165 } 166 167 AICLI_LOG(Repo, Verbose, << "Reading package data to determine version"); 168 Msix::MsixInfo info{ packageLocation }; 169 auto manifest = info.GetAppPackageManifests(); 170 171 THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, manifest.size() > 1); 172 THROW_HR_IF(E_UNEXPECTED, manifest.size() == 0); 173 174 return manifest[0].GetIdentity().GetVersion(); 175 } 176 }; 177 178 // Gets the package family name from the details. 179 std::string GetPackageFamilyNameFromDetails(const SourceDetails& details) 180 { 181 THROW_HR_IF(E_UNEXPECTED, details.Data.empty()); 182 return details.Data; 183 } 184 185 // Creates a name for the cross process reader-writer lock given the details. 186 std::string CreateNameForCPL(const SourceDetails& details) 187 { 188 // The only relevant data is the package family name 189 return "PreIndexedSourceCPL_"s + GetPackageFamilyNameFromDetails(details); 190 } 191 192 // The base class for a package that comes from a preindexed packaged source. 193 struct PreIndexedFactoryBase : public ISourceFactory 194 { 195 std::string_view TypeName() const override final 196 { 197 return PreIndexedPackageSourceFactory::Type(); 198 } 199 200 std::shared_ptr<ISourceReference> Create(const SourceDetails& details) override final 201 { 202 // With more than one source implementation, we will probably need to probe first 203 THROW_HR_IF(E_INVALIDARG, !details.Type.empty() && details.Type != PreIndexedPackageSourceFactory::Type()); 204 205 return CreateInternal(details); 206 } 207 208 virtual std::shared_ptr<ISourceReference> CreateInternal(const SourceDetails& details) = 0; 209 210 bool Add(SourceDetails& details, IProgressCallback& progress) override final 211 { 212 if (details.Type.empty()) 213 { 214 // With more than one source implementation, we will probably need to probe first 215 details.Type = PreIndexedPackageSourceFactory::Type(); 216 AICLI_LOG(Repo, Info, << "Initializing source type: " << details.Name << " => " << details.Type); 217 } 218 else 219 { 220 THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type()); 221 } 222 223 PreIndexedPackageInfo packageInfo(details, [](const std::string& packageLocation) 224 { 225 THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE, Utility::IsUrlRemote(packageLocation) && !Utility::IsUrlSecure(packageLocation)); 226 }); 227 228 AICLI_LOG(Repo, Info, << "Initializing source from: " << details.Name << " => " << packageInfo.PackageLocation()); 229 230 THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, packageInfo.MsixInfo().GetIsBundle()); 231 232 auto fullName = packageInfo.MsixInfo().GetPackageFullName(); 233 AICLI_LOG(Repo, Info, << "Found package full name: " << details.Name << " => " << fullName); 234 235 details.Data = Msix::GetPackageFamilyNameFromFullName(fullName); 236 details.Identifier = Msix::GetPackageFamilyNameFromFullName(fullName); 237 238 auto lock = LockExclusive(details, progress); 239 if (!lock) 240 { 241 return false; 242 } 243 244 return UpdateInternal(packageInfo.PackageLocation(), details, progress); 245 } 246 247 bool Update(const SourceDetails& details, IProgressCallback& progress) override final 248 { 249 return UpdateBase(details, false, progress); 250 } 251 252 bool BackgroundUpdate(const SourceDetails& details, IProgressCallback& progress) override final 253 { 254 return UpdateBase(details, true, progress); 255 } 256 257 // Retrieves the currently cached version of the package. 258 virtual std::optional<Msix::PackageVersion> GetCurrentVersion(const SourceDetails& details) = 0; 259 260 virtual bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress) = 0; 261 262 bool Remove(const SourceDetails& details, IProgressCallback& progress) override final 263 { 264 THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type()); 265 auto lock = LockExclusive(details, progress); 266 if (!lock) 267 { 268 return false; 269 } 270 271 return RemoveInternal(details, progress); 272 } 273 274 virtual bool RemoveInternal(const SourceDetails& details, IProgressCallback&) = 0; 275 276 private: 277 Synchronization::CrossProcessLock LockExclusive(const SourceDetails& details, IProgressCallback& progress, bool isBackground = false) 278 { 279 Synchronization::CrossProcessLock result(CreateNameForCPL(details)); 280 281 if (isBackground) 282 { 283 // If this is a background update, don't wait on the lock. 284 result.TryAcquireNoWait(); 285 } 286 else 287 { 288 result.Acquire(progress); 289 } 290 291 return result; 292 } 293 294 bool UpdateBase(const SourceDetails& details, bool isBackground, IProgressCallback& progress) 295 { 296 THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type()); 297 298 std::optional<Msix::PackageVersion> currentVersion = GetCurrentVersion(details); 299 PreIndexedPackageUpdateCheck updateCheck(details); 300 301 if (currentVersion) 302 { 303 if (currentVersion.value() >= updateCheck.AvailableVersion()) 304 { 305 AICLI_LOG(Repo, Verbose, << "Remote source data (" << updateCheck.AvailableVersion().ToString() << 306 ") was not newer than existing (" << currentVersion.value().ToString() << "), no update needed"); 307 return true; 308 } 309 else 310 { 311 AICLI_LOG(Repo, Verbose, << "Remote source data (" << updateCheck.AvailableVersion().ToString() << 312 ") was newer than existing (" << currentVersion.value().ToString() << "), updating"); 313 } 314 } 315 316 if (progress.IsCancelledBy(CancelReason::Any)) 317 { 318 AICLI_LOG(Repo, Info, << "Cancelling update upon request"); 319 return false; 320 } 321 322 auto lock = LockExclusive(details, progress, isBackground); 323 if (!lock) 324 { 325 return false; 326 } 327 328 return UpdateInternal(updateCheck.PackageLocation(), details, progress); 329 } 330 }; 331 332 // *Should only be called when under a CrossProcessReaderWriteLock* 333 std::optional<Deployment::Extension> GetExtensionFromDetails(const SourceDetails& details) 334 { 335 Deployment::ExtensionCatalog catalog(Deployment::SourceExtensionName); 336 return catalog.FindByPackageFamilyAndId(GetPackageFamilyNameFromDetails(details), Deployment::IndexDBId); 337 } 338 339 std::optional<Msix::PackageVersion> PackagedContextGetCurrentVersion(const SourceDetails& details) 340 { 341 auto extension = GetExtensionFromDetails(details); 342 343 if (extension) 344 { 345 auto version = extension->GetPackageVersion(); 346 return Msix::PackageVersion{ version.Major, version.Minor, version.Build, version.Revision }; 347 } 348 else 349 { 350 return std::nullopt; 351 } 352 } 353 354 // Constructs the location that we will write files to. 355 std::filesystem::path GetStatePathFromDetails(const SourceDetails& details) 356 { 357 std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); 358 result /= PreIndexedPackageSourceFactory::Type(); 359 result /= GetPackageFamilyNameFromDetails(details); 360 return result; 361 } 362 363 std::optional<Msix::PackageVersion> DesktopContextGetCurrentVersion(const SourceDetails& details) 364 { 365 std::filesystem::path packageState = GetStatePathFromDetails(details); 366 std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName; 367 368 if (std::filesystem::exists(packagePath)) 369 { 370 // If we already have a trusted index package, use it to determine if we need to update or not. 371 Msix::WriteLockedMsixFile indexPackage{ packagePath }; 372 if (indexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) 373 { 374 Msix::MsixInfo msixInfo{ packagePath }; 375 auto manifest = msixInfo.GetAppPackageManifests(); 376 377 if (manifest.size() == 1) 378 { 379 return manifest[0].GetIdentity().GetVersion(); 380 } 381 } 382 } 383 384 return std::nullopt; 385 } 386 387 bool CheckForUpdateBeforeOpen(const SourceDetails& details, std::optional<Msix::PackageVersion> currentVersion, const std::optional<TimeSpan>& requestedUpdateInterval) 388 { 389 // If we can't find a good package, then we have to update to operate 390 if (!currentVersion) 391 { 392 AICLI_LOG(Repo, Verbose, << "Source `" << details.Name << "` has no data"); 393 return true; 394 } 395 396 using namespace std::chrono_literals; 397 using clock = std::chrono::system_clock; 398 399 // Attempt to convert the package version to a time_point 400 clock::time_point versionTime = Utility::GetTimePointFromVersion(currentVersion.value()); 401 402 // Since we expect that the version time indicates creation time, don't let it be far in the future. 403 auto now = clock::now(); 404 if (versionTime > now && versionTime - now > 24h) 405 { 406 versionTime = clock::time_point::min(); 407 } 408 409 // Use the later of the version and last update times 410 clock::time_point timeToCheck = (versionTime > details.LastUpdateTime ? versionTime : details.LastUpdateTime); 411 412 return IsAfterUpdateCheckTime(details.Name, timeToCheck, requestedUpdateInterval); 413 } 414 415 struct PackagedContextSourceReference : public ISourceReference 416 { 417 PackagedContextSourceReference(const SourceDetails& details) : m_details(details) 418 { 419 if (!m_details.Data.empty()) 420 { 421 m_details.Identifier = GetPackageFamilyNameFromDetails(details); 422 } 423 } 424 425 std::string GetIdentifier() override { return m_details.Identifier; } 426 427 SourceDetails& GetDetails() override { return m_details; }; 428 429 bool ShouldUpdateBeforeOpen(const std::optional<TimeSpan>& requestedUpdateInterval) override 430 { 431 return CheckForUpdateBeforeOpen(m_details, PackagedContextGetCurrentVersion(m_details), requestedUpdateInterval); 432 } 433 434 std::shared_ptr<ISource> Open(IProgressCallback& progress) override 435 { 436 Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); 437 if (!lock.Acquire(progress)) 438 { 439 return {}; 440 } 441 442 auto extension = GetExtensionFromDetails(m_details); 443 if (!extension) 444 { 445 AICLI_LOG(Repo, Info, << "Package not found " << m_details.Data); 446 THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); 447 } 448 449 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NEEDS_REMEDIATION), !extension->VerifyContentIntegrity(progress)); 450 451 // To work around an issue with accessing the public folder, we are temporarily 452 // constructing the location ourself. This was already the case for the non-packaged 453 // runtime, and we can fix both in the future. The only problem with this is that 454 // the directory in the extension *must* be Public, rather than one set by the creator. 455 std::filesystem::path indexLocation = extension->GetPackagePath(); 456 indexLocation /= s_PreIndexedPackageSourceFactory_IndexFilePath; 457 458 SQLiteIndex index = SQLiteIndex::Open(indexLocation.u8string(), SQLiteIndex::OpenDisposition::Immutable); 459 460 // We didn't use to store the source identifier, so we compute it here in case it's 461 // missing from the details. 462 m_details.Identifier = GetPackageFamilyNameFromDetails(m_details); 463 return std::make_shared<SQLiteIndexSource>(m_details, std::move(index), false, true); 464 } 465 466 private: 467 SourceDetails m_details; 468 }; 469 470 // Source factory for running within a packaged context 471 struct PackagedContextFactory : public PreIndexedFactoryBase 472 { 473 std::shared_ptr<ISourceReference> CreateInternal(const SourceDetails& details) override 474 { 475 return std::make_shared<PackagedContextSourceReference>(details); 476 } 477 478 std::optional<Msix::PackageVersion> GetCurrentVersion(const SourceDetails& details) override 479 { 480 return PackagedContextGetCurrentVersion(details); 481 } 482 483 bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress) override 484 { 485 // Due to complications with deployment, download the file and deploy from 486 // a local source while we investigate further. 487 bool download = Utility::IsUrlRemote(packageLocation); 488 std::filesystem::path localFile; 489 490 if (download) 491 { 492 localFile = Runtime::GetPathTo(Runtime::PathName::Temp); 493 localFile /= GetPackageFamilyNameFromDetails(details) + ".msix"; 494 495 Utility::Download(packageLocation, localFile, Utility::DownloadType::Index, progress); 496 } 497 else 498 { 499 localFile = Utility::ConvertToUTF16(packageLocation); 500 } 501 502 // Verify the local file 503 Msix::WriteLockedMsixFile fileLock{ localFile }; 504 Msix::MsixInfo localMsixInfo{ localFile }; 505 506 // The package should not be a bundle 507 THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, localMsixInfo.GetIsBundle()); 508 509 // Ensure that family name has not changed 510 THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, 511 GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(localMsixInfo.GetPackageFullName())); 512 513 if (!fileLock.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) 514 { 515 AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation."); 516 THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); 517 } 518 519 winrt::Windows::Foundation::Uri uri = winrt::Windows::Foundation::Uri(localFile.c_str()); 520 Deployment::AddPackage( 521 uri, 522 Deployment::Options{ WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::Trusted) }, 523 progress); 524 525 if (download) 526 { 527 try 528 { 529 // If successful, delete the file 530 std::filesystem::remove(localFile); 531 } 532 CATCH_LOG(); 533 } 534 535 return true; 536 } 537 538 bool RemoveInternal(const SourceDetails& details, IProgressCallback& callback) override 539 { 540 auto fullName = Msix::GetPackageFullNameFromFamilyName(GetPackageFamilyNameFromDetails(details)); 541 542 if (!fullName) 543 { 544 AICLI_LOG(Repo, Info, << "No full name found for family name: " << GetPackageFamilyNameFromDetails(details)); 545 } 546 else 547 { 548 AICLI_LOG(Repo, Info, << "Removing package: " << *fullName); 549 Deployment::RemovePackage(*fullName, winrt::Windows::Management::Deployment::RemovalOptions::None, callback); 550 } 551 552 return true; 553 } 554 }; 555 556 struct DesktopContextSourceReference : public ISourceReference 557 { 558 DesktopContextSourceReference(const SourceDetails& details) : m_details(details) 559 { 560 if (!m_details.Data.empty()) 561 { 562 m_details.Identifier = GetPackageFamilyNameFromDetails(details); 563 } 564 } 565 566 std::string GetIdentifier() override { return m_details.Identifier; } 567 568 SourceDetails& GetDetails() override { return m_details; }; 569 570 bool ShouldUpdateBeforeOpen(const std::optional<TimeSpan>& requestedUpdateInterval) override 571 { 572 return CheckForUpdateBeforeOpen(m_details, DesktopContextGetCurrentVersion(m_details), requestedUpdateInterval); 573 } 574 575 std::shared_ptr<ISource> Open(IProgressCallback& progress) override 576 { 577 Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); 578 if (!lock.Acquire(progress)) 579 { 580 return {}; 581 } 582 583 std::filesystem::path packageLocation = GetStatePathFromDetails(m_details); 584 packageLocation /= s_PreIndexedPackageSourceFactory_PackageFileName; 585 586 if (!std::filesystem::exists(packageLocation)) 587 { 588 AICLI_LOG(Repo, Info, << "Data not found at " << packageLocation); 589 THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); 590 } 591 592 // Put a write exclusive lock on the index package. 593 Msix::WriteLockedMsixFile indexPackage{ packageLocation }; 594 595 // Validate index package trust info. 596 THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, !indexPackage.ValidateTrustInfo(WI_IsFlagSet(m_details.TrustLevel, SourceTrustLevel::StoreOrigin))); 597 598 // Create a temp lock exclusive index file. 599 auto tempIndexFilePath = Runtime::GetNewTempFilePath(); 600 auto tempIndexFile = Utility::ManagedFile::CreateWriteLockedFile(tempIndexFilePath, GENERIC_WRITE, true); 601 602 // Populate temp index file. 603 Msix::MsixInfo packageInfo(packageLocation); 604 packageInfo.WriteToFileHandle(s_PreIndexedPackageSourceFactory_IndexFilePath, tempIndexFile.GetFileHandle(), progress); 605 606 if (progress.IsCancelledBy(CancelReason::Any)) 607 { 608 AICLI_LOG(Repo, Info, << "Cancelling open upon request"); 609 return {}; 610 } 611 612 SQLiteIndex index = SQLiteIndex::Open(tempIndexFile.GetFilePath().u8string(), SQLiteIndex::OpenDisposition::Immutable, std::move(tempIndexFile)); 613 614 // We didn't use to store the source identifier, so we compute it here in case it's 615 // missing from the details. 616 m_details.Identifier = GetPackageFamilyNameFromDetails(m_details); 617 return std::make_shared<SQLiteIndexSource>(m_details, std::move(index), false, true); 618 } 619 620 private: 621 SourceDetails m_details; 622 }; 623 624 // Source factory for running outside of a package. 625 struct DesktopContextFactory : public PreIndexedFactoryBase 626 { 627 std::shared_ptr<ISourceReference> CreateInternal(const SourceDetails& details) override 628 { 629 return std::make_shared<DesktopContextSourceReference>(details); 630 } 631 632 std::optional<Msix::PackageVersion> GetCurrentVersion(const SourceDetails& details) override 633 { 634 return DesktopContextGetCurrentVersion(details); 635 } 636 637 bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress) override 638 { 639 // We will extract the manifest and index files directly to this location 640 std::filesystem::path packageState = GetStatePathFromDetails(details); 641 std::filesystem::create_directories(packageState); 642 643 std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName; 644 645 std::filesystem::path tempPackagePath = packagePath.u8string() + ".dnld.msix"; 646 auto removeTempFileOnExit = wil::scope_exit([&]() 647 { 648 try 649 { 650 std::filesystem::remove(tempPackagePath); 651 } 652 catch (...) 653 { 654 AICLI_LOG(Repo, Info, << "Failed to remove temp index file at: " << tempPackagePath); 655 } 656 }); 657 658 if (Utility::IsUrlRemote(packageLocation)) 659 { 660 AppInstaller::Utility::Download(packageLocation, tempPackagePath, AppInstaller::Utility::DownloadType::Index, progress); 661 } 662 else 663 { 664 std::filesystem::copy(packageLocation, tempPackagePath); 665 progress.OnProgress(100, 100, ProgressType::Percent); 666 } 667 668 if (progress.IsCancelledBy(CancelReason::Any)) 669 { 670 AICLI_LOG(Repo, Info, << "Cancelling update upon request"); 671 return false; 672 } 673 674 { 675 // Extra scope to release the file lock right after trust validation. 676 Msix::WriteLockedMsixFile tempIndexPackage{ tempPackagePath }; 677 Msix::MsixInfo tempMsixInfo{ tempPackagePath }; 678 679 // The package should not be a bundle 680 THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, tempMsixInfo.GetIsBundle()); 681 682 // Ensure that family name has not changed 683 THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, 684 GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(tempMsixInfo.GetPackageFullName())); 685 686 if (!tempIndexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) 687 { 688 AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation."); 689 THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); 690 } 691 } 692 693 std::filesystem::rename(tempPackagePath, packagePath); 694 AICLI_LOG(Repo, Info, << "Source update success."); 695 696 removeTempFileOnExit.release(); 697 698 return true; 699 } 700 701 bool RemoveInternal(const SourceDetails& details, IProgressCallback&) override 702 { 703 std::filesystem::path packageState = GetStatePathFromDetails(details); 704 705 if (!std::filesystem::exists(packageState)) 706 { 707 AICLI_LOG(Repo, Info, << "No state found for source: " << packageState.u8string()); 708 } 709 else 710 { 711 AICLI_LOG(Repo, Info, << "Removing state found for source: " << packageState.u8string()); 712 std::filesystem::remove_all(packageState); 713 } 714 715 return true; 716 } 717 }; 718 } 719 720 std::unique_ptr<ISourceFactory> PreIndexedPackageSourceFactory::Create() 721 { 722 if (Runtime::IsRunningInPackagedContext()) 723 { 724 return std::make_unique<PackagedContextFactory>(); 725 } 726 else 727 { 728 return std::make_unique<DesktopContextFactory>(); 729 } 730 } 731 }