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 names should not be case sensitive #226

Open
matt-phylum opened this issue Mar 31, 2023 · 6 comments · May be fixed by #301
Open

NuGet package names should not be case sensitive #226

matt-phylum opened this issue Mar 31, 2023 · 6 comments · May be fixed by #301

Comments

@matt-phylum
Copy link
Contributor

The spec says nothing about how NuGet package names should be normalized, but the test suite ensures that NuGet package names are case sensitive.

This is not true about NuGet package names. The NuGet API documentation is very specific about how the names are converted to lowercase on the client side before making a request to the API: https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource This precludes the ability for multiple packages to exist with names that vary only by capitalization because the package data for all such packages would need to exist simultaneously at the same address.

This can be verified by installing packages:

$ docker run --rm -it mcr.microsoft.com/dotnet/sdk:6.0
root@f796b64be31c:/# mkdir test
root@f796b64be31c:/# cd test
root@f796b64be31c:/test# dotnet new console
The template "Console App" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on /test/test.csproj...
  Determining projects to restore...
  Restored /test/test.csproj (in 51 ms).
Restore succeeded.


root@f796b64be31c:/test# dotnet add package NeWtOnSoFt.jSoN
  Determining projects to restore...
  Writing /tmp/tmpjQ7OCK.tmp
info : X.509 certificate chain validation will use the fallback certificate bundle at '/usr/share/dotnet/sdk/6.0.407/trustedroots/codesignctl.pem'.
info : Adding PackageReference for package 'NeWtOnSoFt.jSoN' into project '/test/test.csproj'.
info :   GET https://api.nuget.org/v3/registration5-gz-semver2/newtonsoft.json/index.json
info :   OK https://api.nuget.org/v3/registration5-gz-semver2/newtonsoft.json/index.json 48ms
info : Restoring packages for /test/test.csproj...
info :   GET https://api.nuget.org/v3-flatcontainer/newtonsoft.json/index.json
info :   OK https://api.nuget.org/v3-flatcontainer/newtonsoft.json/index.json 45ms
info :   GET https://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg
info :   OK https://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg 33ms
info : Installed Newtonsoft.Json 13.0.3 from https://api.nuget.org/v3/index.json with content hash HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==.
info : Package 'NeWtOnSoFt.jSoN' is compatible with all the specified frameworks in project '/test/test.csproj'.
info : PackageReference for package 'NeWtOnSoFt.jSoN' version '13.0.3' added to file '/test/test.csproj'.
info : Writing assets file to disk. Path: /test/obj/project.assets.json
log  : Restored /test/test.csproj (in 518 ms).
root@f796b64be31c:/test# cat test.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="NeWtOnSoFt.jSoN" Version="13.0.3" />
  </ItemGroup>

</Project>

Even though I used clearly the wrong capitalization, the HTTP requests use lowercase names and the correct package is found.

I don't know if the spec should be expanded to specify that NuGet names are case insensitive, but the tests asserting that the package name is case sensitive should probably be removed.

@stevespringett
Copy link
Member

Is the same true for non-Microsoft implementations of nuget? Are Artifactory or Nexus Repo both case-insensitive with nuget packages.

I found something interesting. It appears that even if the package name is case insensitive, the purl subpath will be case sensitive according to https://learn.microsoft.com/en-us/nuget/create-packages/supporting-multiple-target-frameworks

@stevespringett
Copy link
Member

Do we know if Nuget 1 and 2 APIs stated that they were case insensitive, or is this specific to nuget 3?

@matt-phylum
Copy link
Contributor Author

For V3, since the client is required to convert to lowercase, it shouldn't matter what server implementation is being used. They all must be case insensitive or else only lowercase packages would be downloadable, and most packages contain at least one uppercase character.

V3 is the first officially documented API. For V2, the unofficial docs say the package ID is case insensitive. However, since the V2 API is OData instead of just files in case-sensitive buckets, the client can send the mixed case package names and I guess it's possible that somebody could make a weird server that has case sensitive package IDs. It'd cause compatibility issues.

The documentation for package authoring has said that package IDs are case insensitive since 2016. Before this it said nothing about case sensitivity.

The modern NuGet client code has considered two packages to be the same if their case insensitive IDs match since 2014.

In the old NuGet repository, I found code from the initial commit in August 2010 to skip installing a package if another package having the same case-insensitive package ID and satisfying the version requirement is already installed. The first preview release was announced over a month later.

I think it's safe to say that the names have always been case insensitive.

@prabhu
Copy link

prabhu commented Sep 4, 2023

We found that lower casing Nuget packages in the sbom broke several tools, open-source and commercial. So, cdxgen retains the original case from the manifest in the sbom but lower cases only when using the NuGet API.

https://github.com/CycloneDX/cdxgen/blob/master/utils.js#L4432

@matt-phylum
Copy link
Contributor Author

Having interoperability issues seems possible if software was written against the old spec and performs case sensitive ID comparisons against some other source. However, the capitalization needs to be fixed because software that is incorrectly treating the ID as case sensitive will have the same problems when package names are received in with different capitalization. In old .Net, because the complete transitive dependency graph needed to be stored in packages.config and development was almost always done in Visual Studio, developers rarely typed the names of dependencies so it was a pretty safe bet that the package ID would always been seen with the same capitalization. However, in modern .Net, it's not that uncommon for somebody to open their .csproj file in a text editor and type in a new <PackageReference Include="Newtonsoft.JSON" Version="13.0.3" /> or something, in which case tools that process the csproj file must be prepared to accept packages with different capitalization or they will treat Newtonsoft.JSON as a distinct package from Newtonsoft.Json.

@prabhu Unrelated to this issue: it looks like that cdxgen code makes a few mistakes with the way NuGet is handled. NuGet packages do not come in groups (namespaces?), and the prefix cannot be used to determine the license. cdxgen assumes if the ID starts with "microsoft" that it will be licensed under a .NET library license, but this seems unlikely since years ago when Microsoft started open sourcing almost every general .Net library they publish to NuGet. For example, Microsoft.Extensions.Primitives is licensed MIT. Other Microsoft libraries are licensed differently, eg Microsoft.CrmSdk.Workflow is licensed under a Microsoft Power Apps SDK license. The same is true for System, where System.Data.SQLite is public domain, and not published by Microsoft. It looks like the caching is also wrong because it's caching based on the first two name segments, and, unlike NPM, name segments don't really mean anything in NuGet. For example, Serilog.Enrichers.Span Serilog.Enrichers.Context are published by different people and have different licenses.

@prabhu
Copy link

prabhu commented Sep 5, 2023

Thanks @matt-phylum. Let me check and fix these issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants