diff --git a/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs b/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs index d2d340ccefb1..62852834de27 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs +++ b/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs @@ -16,7 +16,8 @@ Task DownloadPackageAsync(PackageId packageId, bool includePreview = false, bool? includeUnlisted = null, DirectoryPath? downloadFolder = null, - PackageSourceMapping packageSourceMapping = null); + PackageSourceMapping packageSourceMapping = null, + bool isTool = false); Task GetPackageUrl(PackageId packageId, NuGetVersion packageVersion = null, diff --git a/src/Cli/dotnet/NugetPackageDownloader/LocalizableStrings.resx b/src/Cli/dotnet/NugetPackageDownloader/LocalizableStrings.resx index f5e51e2e900d..fd22df9f371a 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/LocalizableStrings.resx +++ b/src/Cli/dotnet/NugetPackageDownloader/LocalizableStrings.resx @@ -126,6 +126,9 @@ {0} is not found in NuGet feeds {1}. + + Package {0} is not a .NET tool. + Downloading {0} version {1} failed. diff --git a/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs b/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs index c1c4d9318862..4c1176656a8e 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs +++ b/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs @@ -83,19 +83,45 @@ public async Task DownloadPackageAsync(PackageId packageId, bool includePreview = false, bool? includeUnlisted = null, DirectoryPath? downloadFolder = null, - PackageSourceMapping packageSourceMapping = null) + PackageSourceMapping packageSourceMapping = null, + bool isTool = false) { CancellationToken cancellationToken = CancellationToken.None; (var source, var resolvedPackageVersion) = await GetPackageSourceAndVersion(packageId, packageVersion, packageSourceLocation, includePreview, includeUnlisted ?? packageVersion is not null, packageSourceMapping).ConfigureAwait(false); - FindPackageByIdResource resource = null; SourceRepository repository = GetSourceRepository(source); - resource = await repository.GetResourceAsync(cancellationToken) - .ConfigureAwait(false); + // TODO: Fix this to use the PackageSearchResourceV3 once https://github.com/NuGet/NuGet.Client/pull/5991 is completed. + if (isTool && await repository.GetResourceAsync().ConfigureAwait(false) is ServiceIndexResourceV3 serviceIndex) + { + // See https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-manifest-nuspec + var uri = serviceIndex.GetServiceEntries("PackageBaseAddress/3.0.0")[0].Uri; + var queryUri = uri + $"{packageId}/{packageVersion}/{packageId}.nuspec"; + + using HttpClient client = new(new HttpClientHandler() { CheckCertificateRevocationList = true }); + using HttpResponseMessage response = await client.GetAsync(queryUri).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + string nuspec = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + XDocument doc = XDocument.Parse(nuspec); + + if (!ToolPackageInstance.IsToolPackage(doc)) + { + throw new GracefulException(string.Format(LocalizableStrings.NotATool, packageId)); + } + } + else + { + throw new GracefulException(string.Format(LocalizableStrings.NotATool, packageId)); + } + } + FindPackageByIdResource resource = await repository.GetResourceAsync(cancellationToken) + .ConfigureAwait(false); if (resource == null) { throw new NuGetPackageNotFoundException( diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.cs.xlf index b8746dd50ae4..3480a5e9bf8b 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.cs.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.cs.xlf @@ -37,6 +37,11 @@ Balíček {0} se nenašel v informačních kanálech NuGet {1}. + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. Přeskakuje se ověření podpisu balíčku NuGet. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.de.xlf index d7f2feaee60f..0fac6d8582b4 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.de.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.de.xlf @@ -37,6 +37,11 @@ "{0}" wurde in NuGet-Feeds "{1}" nicht gefunden. + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. Die Überprüfung der NuGet-Paketsignatur wird übersprungen. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.es.xlf index 8f7665c41707..0bc2862f84b8 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.es.xlf @@ -37,6 +37,11 @@ No se encuentra {0} en las fuentes de NuGet {1}. + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. Omitiendo la comprobación de la firma del paquete NuGet. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.fr.xlf index 371e037ff4cb..7b3151f742d7 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.fr.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.fr.xlf @@ -37,6 +37,11 @@ {0} est introuvable dans les flux NuGet {1}. + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. La vérification de la signature du package NuGet est ignorée. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.it.xlf index 49643fd5d074..6c62d8290b43 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.it.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.it.xlf @@ -37,6 +37,11 @@ {0} non è stato trovato nei feed NuGet {1}. + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. La verifica della firma del pacchetto NuGet verrà ignorata. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ja.xlf index 0471c7d97648..90b352e1cb3c 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ja.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ja.xlf @@ -37,6 +37,11 @@ {0} が NuGet フィード {1} に見つかりません。 + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. NuGet パッケージ署名の認証をスキップしています。 diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ko.xlf index 5a17478c9016..748ddbf713e5 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ko.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ko.xlf @@ -37,6 +37,11 @@ NuGet 피드 {1} 에서 {0}을(를) 찾을 수 없습니다. + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. NuGet 패키지 서명 확인을 건너뛰는 중입니다. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pl.xlf index daa713f0e271..ee5caf8ab2a0 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pl.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pl.xlf @@ -37,6 +37,11 @@ Nie znaleziono pakietu {0} w kanałach informacyjnych NuGet {1}. + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. Pomijanie weryfikacji podpisu pakietu NuGet. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pt-BR.xlf index eccae44c1a30..49bcdfa31490 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pt-BR.xlf @@ -37,6 +37,11 @@ {0} não foi encontrado no NuGet feeds {1}. + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. Ignorando a verificação de assinatura do pacote NuGet. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ru.xlf index 0bf016d6bd56..fd39c2082e1c 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ru.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ru.xlf @@ -37,6 +37,11 @@ {0} не найдено в веб-каналах NuGet {1}. + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. Пропуск проверки подписи пакета NuGet. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.tr.xlf index 4e8dc04150e3..f14f6c3ad5d0 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.tr.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.tr.xlf @@ -37,6 +37,11 @@ {0}, {1} NuGet akışlarında bulunamadı. + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. NuGet paket imzası doğrulaması atlanıyor. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hans.xlf index f7303342d2a0..d5c7630d016b 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hans.xlf @@ -37,6 +37,11 @@ 在 NuGet 源 {1} 中找不到 {0}。 + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. 正在跳过 NuGet 包签名验证。 diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hant.xlf index 37eece9cf07e..40e880e4c730 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hant.xlf @@ -37,6 +37,11 @@ 在 NuGet 摘要 {1} 中找不到 {0}"。 + + Package {0} is not a .NET tool. + Package {0} is not a .NET tool. + + Skipping NuGet package signature verification. 正在略過 NuGet 套件簽章驗證。 diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs index 84d07f2eef12..05af9e43115d 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs @@ -134,13 +134,21 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa { DownloadAndExtractPackage(packageId, nugetPackageDownloader, toolDownloadDir.Value, packageVersion, packageSourceLocation, includeUnlisted: givenSpecificVersion).GetAwaiter().GetResult(); } - else if(isGlobalTool) + else { - throw new ToolPackageException( - string.Format( - CommonLocalizableStrings.ToolPackageConflictPackageId, - packageId, - packageVersion.ToNormalizedString())); + if (!ToolPackageInstance.IsToolPackage(package.Nuspec.Xml)) + { + throw new GracefulException(string.Format(NuGetPackageDownloader.LocalizableStrings.NotATool, packageId)); + } + + if (isGlobalTool) + { + throw new ToolPackageException( + string.Format( + CommonLocalizableStrings.ToolPackageConflictPackageId, + packageId, + packageVersion.ToNormalizedString())); + } } CreateAssetFile(packageId, packageVersion, toolDownloadDir, assetFileDirectory, _runtimeJsonPath, targetFramework); @@ -284,7 +292,7 @@ private static async Task DownloadAndExtractPackage( bool includeUnlisted = false ) { - var packagePath = await nugetPackageDownloader.DownloadPackageAsync(packageId, packageVersion, packageSourceLocation, includeUnlisted: includeUnlisted).ConfigureAwait(false); + var packagePath = await nugetPackageDownloader.DownloadPackageAsync(packageId, packageVersion, packageSourceLocation, includeUnlisted: includeUnlisted, isTool: true).ConfigureAwait(false); // look for package on disk and read the version NuGetVersion version; diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs b/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs index f026f370567e..d9ced1ecb703 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs @@ -23,6 +23,17 @@ public static ToolPackageInstance CreateFromAssetFile(PackageId id, DirectoryPat return new ToolPackageInstance(id, version, packageDirectory, assetsJsonParentDirectory); } + + /// + /// Validates that the nuspec XML represents a .NET tool package. + /// + /// The nuspec XML to check. + /// if the nuspec represents a .NET tool package; otherwise, . + public static bool IsToolPackage(XDocument nuspec) => + nuspec.Root.Descendants().Where( + e => e.Name.LocalName == "packageType" && + e.Attributes().Where(a => a.Name.LocalName == "name" && a.Value == "DotnetTool").Any()).Any(); + private const string PackagedShimsDirectoryConvention = "shims"; public IEnumerable Warnings => _toolConfiguration.Value.Warnings; diff --git a/test/dotnet-workload-install.Tests/FailingNuGetPackageInstaller.cs b/test/dotnet-workload-install.Tests/FailingNuGetPackageInstaller.cs index 6f10a82923e5..9906e45e7af3 100644 --- a/test/dotnet-workload-install.Tests/FailingNuGetPackageInstaller.cs +++ b/test/dotnet-workload-install.Tests/FailingNuGetPackageInstaller.cs @@ -23,7 +23,8 @@ public Task DownloadPackageAsync(PackageId packageId, NuGetVersion packa bool includePreview = false, bool? includeUnlisted = null, DirectoryPath? downloadFolder = null, - PackageSourceMapping packageSourceMapping = null) + PackageSourceMapping packageSourceMapping = null, + bool isTool = false) { var mockPackagePath = Path.Combine(MockPackageDir, $"{packageId}.{packageVersion}.nupkg"); File.WriteAllText(mockPackagePath, string.Empty); diff --git a/test/dotnet-workload-install.Tests/MockNuGetPackageInstaller.cs b/test/dotnet-workload-install.Tests/MockNuGetPackageInstaller.cs index 84c60802bd1d..ea2be4215582 100644 --- a/test/dotnet-workload-install.Tests/MockNuGetPackageInstaller.cs +++ b/test/dotnet-workload-install.Tests/MockNuGetPackageInstaller.cs @@ -38,7 +38,8 @@ public Task DownloadPackageAsync(PackageId packageId, bool includePreview = false, bool? includeUnlisted = null, DirectoryPath? downloadFolder = null, - PackageSourceMapping packageSourceMapping = null) + PackageSourceMapping packageSourceMapping = null, + bool isTool = false) { DownloadCallParams.Add((packageId, packageVersion, downloadFolder, packageSourceLocation));