Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nuget package versions are case insensitive #358

Merged
merged 4 commits into from
Oct 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Shared.CLI/Shared.CLI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
<PackageReference Include="NLog" Version="4.7.13" />
<PackageReference Include="NLog.Schema" Version="4.7.13" />
<PackageReference Include="NuGet.Versioning" Version="6.1.0" />
<PackageReference Include="NuGet.Versioning" Version="6.2.2" />
<PackageReference Include="Octokit" Version="0.50.0" />
<PackageReference Include="Sarif.Sdk" Version="2.4.12" />
<PackageReference Include="SemanticVersioning" Version="2.0.0" />
Expand Down
52 changes: 35 additions & 17 deletions src/Shared/PackageManagers/NuGetProjectManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace Microsoft.CST.OpenSource.PackageManagers
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;

public class NuGetProjectManager : TypedManager<NuGetPackageVersionMetadata, NuGetProjectManager.NuGetArtifactType>
{
Expand Down Expand Up @@ -149,7 +150,6 @@ private async Task<string> GetRegistrationEndpointAsync()
throw new InvalidOperationException($"Can't find the latest version of {purl}");
packageVersion = latestVersion;
}

// Construct a new PackageURL that's guaranteed to have a version.
PackageURL purlWithVersion = new (purl.Type, purl.Namespace, packageName, packageVersion, purl.Qualifiers, purl.Subpath);

Expand Down Expand Up @@ -177,7 +177,7 @@ public override Uri GetPackageAbsoluteUri(PackageURL purl)
throw new InvalidOperationException($"Can't find the latest version of {purl}");;

// Construct a new PackageURL that's guaranteed to have a version, the latest version is used if no version was provided.
PackageURL purlWithVersion = !string.IsNullOrWhiteSpace(purl.Version) ?
PackageURL purlWithVersion = !string.IsNullOrWhiteSpace(purl.Version) ?
purl : new PackageURL(purl.Type, purl.Namespace, purl.Name, latestVersion, purl.Qualifiers, purl.Subpath);

NuGetPackageVersionMetadata? packageVersionMetadata =
Expand Down Expand Up @@ -207,61 +207,79 @@ public override Uri GetPackageAbsoluteUri(PackageURL purl)

return metadata;
}

/// <inheritdoc />
public override async Task<bool> PackageVersionExistsAsync(PackageURL purl, bool useCache = true)
{
Logger.Trace("PackageExists {0}", purl?.ToString());
if (purl is null)
{
Logger.Trace("Provided PackageURL was null.");
return false;
}

if(purl.Version.IsBlank())
{
Logger.Trace("Provided PackageURL version was null or blank.");
return false;
}

// NuGet packages are case insensitive.
return (await EnumerateVersionsAsync(purl, useCache)).Contains(purl.Version, StringComparer.InvariantCultureIgnoreCase);
}

/// <summary>
/// Updates the package version specific values in <see cref="PackageMetadata"/>.
/// </summary>
/// <param name="metadata">The <see cref="PackageMetadata"/> object to update with the values for this version.</param>
/// <param name="packageVersionPackageVersionMetadata">The <see cref="NuGetPackageVersionMetadata"/> representing this version.</param>
private async Task UpdateVersionMetadata(PackageMetadata metadata, NuGetPackageVersionMetadata packageVersionPackageVersionMetadata)
/// <param name="packageVersionMetadata">The <see cref="NuGetPackageVersionMetadata"/> representing this version.</param>
private async Task UpdateVersionMetadata(PackageMetadata metadata, NuGetPackageVersionMetadata packageVersionMetadata)
{
if (metadata.PackageVersion is null)
{
return;
}

string nameLowercase = packageVersionPackageVersionMetadata.Name.ToLowerInvariant();

// Set the version specific URI values.
metadata.VersionUri = $"{metadata.PackageManagerUri}/packages/{nameLowercase}/{metadata.PackageVersion}";
metadata.ApiVersionUri = packageVersionPackageVersionMetadata.CatalogUri.ToString();
metadata.VersionUri = $"{metadata.PackageManagerUri}/packages/{packageVersionMetadata.Name}/{metadata.PackageVersion}";
metadata.ApiVersionUri = packageVersionMetadata.CatalogUri.ToString();

// Construct the artifact contents url.
metadata.VersionDownloadUri = GetNupkgUrl(packageVersionPackageVersionMetadata.Name, metadata.PackageVersion);
metadata.VersionDownloadUri = GetNupkgUrl(packageVersionMetadata.Name, metadata.PackageVersion);

// TODO: size and hash

// Homepage url
metadata.Homepage = packageVersionPackageVersionMetadata.ProjectUrl?.ToString();
metadata.Homepage = packageVersionMetadata.ProjectUrl?.ToString();

// Authors and Maintainers
UpdateMetadataAuthorsAndMaintainers(metadata, packageVersionPackageVersionMetadata);
UpdateMetadataAuthorsAndMaintainers(metadata, packageVersionMetadata);

// Repository
await UpdateMetadataRepository(metadata);

// Dependencies
IList<PackageDependencyGroup> dependencyGroups = packageVersionPackageVersionMetadata.DependencySets.ToList();
IList<PackageDependencyGroup> dependencyGroups = packageVersionMetadata.DependencySets.ToList();
metadata.Dependencies ??= dependencyGroups.SelectMany(group => group.Packages, (dependencyGroup, package) => new { dependencyGroup, package})
.Select(dependencyGroupAndPackage => new Dependency() { Package = dependencyGroupAndPackage.package.ToString(), Framework = dependencyGroupAndPackage.dependencyGroup.TargetFramework?.ToString()})
.ToList();

// Keywords
metadata.Keywords = new List<string>(packageVersionPackageVersionMetadata.Tags.Split(", "));
metadata.Keywords = new List<string>(packageVersionMetadata.Tags.Split(", "));

// Licenses
if (packageVersionPackageVersionMetadata.LicenseMetadata is not null)
if (packageVersionMetadata.LicenseMetadata is not null)
{
metadata.Licenses ??= new List<License>();
metadata.Licenses.Add(new License()
{
Name = packageVersionPackageVersionMetadata.LicenseMetadata.License,
Url = packageVersionPackageVersionMetadata.LicenseMetadata.LicenseUrl.ToString()
Name = packageVersionMetadata.LicenseMetadata.License,
Url = packageVersionMetadata.LicenseMetadata.LicenseUrl.ToString()
});
}

// publishing info
metadata.UploadTime = packageVersionPackageVersionMetadata.Published?.DateTime;
metadata.UploadTime = packageVersionMetadata.Published?.DateTime;
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Shared/Shared.Lib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="6.0.0" />
<PackageReference Include="NLog" Version="4.7.12" />
<PackageReference Include="NLog.Schema" Version="4.7.12" />
<PackageReference Include="NuGet.Packaging" Version="6.1.0" />
<PackageReference Include="NuGet.Packaging" Version="6.2.2" />
<PackageReference Include="NuGet.Protocol" Version="6.2.2" />
<PackageReference Include="Octokit" Version="0.50.0" />
<PackageReference Include="packageurl-dotnet" Version="1.1.0" />
Expand Down
18 changes: 16 additions & 2 deletions src/oss-tests/ProjectManagerTests/NuGetProjectManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ public class NuGetProjectManagerTests
{ "https://api.nuget.org/v3/registration5-gz-semver2/razorengine/index.json", Resources.razorengine_json },
{ "https://api.nuget.org/v3/catalog0/data/2022.03.11.23.17.27/razorengine.4.2.3-beta1.json", Resources.razorengine_4_2_3_beta1_json },
}.ToImmutableDictionary();

// Map PackageURLs to metadata as json.
private readonly IDictionary<string, string> _metadata = new Dictionary<string, string>()
{
{ "pkg:nuget/[email protected]", Resources.razorengine_4_2_3_beta1_metadata_json },
{ "pkg:nuget/razorengine", Resources.razorengine_latest_metadata_json },
}.ToImmutableDictionary();

// Map PackageURLs to the list of versions as json.
private readonly IDictionary<string, string> _versions = new Dictionary<string, string>()
{
Expand Down Expand Up @@ -73,6 +73,20 @@ public NuGetProjectManagerTests()
_projectManager = new NuGetProjectManager(".", new NuGetPackageActions(), _httpFactory);
}

[DataTestMethod]
[DataRow("pkg:nuget/[email protected]")]
[DataRow("pkg:nuget/[email protected]")]
[DataRow("pkg:nuget/[email protected]")]
public async Task TestNugetCaseInsensitiveHandlingPackageExistsSucceeds(string purlString)
{
PackageURL purl = new(purlString);
_projectManager = new NuGetProjectManager(".", null, _httpFactory);

bool exists = await _projectManager.PackageVersionExistsAsync(purl, useCache: false);

Assert.IsTrue(exists);
}

[DataTestMethod]
[DataRow("pkg:nuget/[email protected]", "RazorEngine - A Templating Engine based on the Razor parser.", "Matthew Abbott, Ben Dornis, Matthias Dittrich")] // Normal package
[DataRow("pkg:nuget/razorengine", "RazorEngine - A Templating Engine based on the Razor parser.", "Matthew Abbott, Ben Dornis, Matthias Dittrich", "4.5.1-alpha001")] // Normal package, no specified version
Expand Down