diff --git a/src/packageurl/__init__.py b/src/packageurl/__init__.py index 3bfae87..e0ed725 100644 --- a/src/packageurl/__init__.py +++ b/src/packageurl/__init__.py @@ -199,7 +199,6 @@ def normalize_name( "composer", "luarocks", "oci", - "npm", "alpm", "apk", "bitnami", diff --git a/src/packageurl/validate.py b/src/packageurl/validate.py index 4ee81c3..ee0111f 100644 --- a/src/packageurl/validate.py +++ b/src/packageurl/validate.py @@ -585,8 +585,8 @@ class NpmTypeDefinition(BasePurlType): default_repository_url = "https://registry.npmjs.org/" namespace_requirement = "optional" allowed_qualifiers = {"repository_url"} - namespace_case_sensitive = False - name_case_sensitive = False + namespace_case_sensitive = True + name_case_sensitive = True version_case_sensitive = True purl_pattern = "pkg:npm/.*" diff --git a/tests/test_packageurl.py b/tests/test_packageurl.py index 2c36549..937b27c 100644 --- a/tests/test_packageurl.py +++ b/tests/test_packageurl.py @@ -388,3 +388,22 @@ def test_no_encoding_to_string(): p.to_string(encode=False) == "pkg:nuget/an:odd:space/libiconv: character set conversion library@1.9?package-id=e11a609df352e292" ) + + +def test_npm_name_case_is_preserved_on_parse(): + purl = PackageURL.from_string("pkg:npm/Orchestrator@1.0.0") + assert purl.name == "Orchestrator" + assert purl.to_string() == "pkg:npm/Orchestrator@1.0.0" + + +def test_npm_scoped_name_case_is_preserved_on_parse(): + purl = PackageURL.from_string("pkg:npm/%40Scope/MyPackage@1.0.0") + assert purl.namespace == "@Scope" + assert purl.name == "MyPackage" + assert purl.to_string() == "pkg:npm/%40Scope/MyPackage@1.0.0" + + +def test_npm_uppercase_name_has_no_case_warning(): + messages = PackageURL.validate_string(purl="pkg:npm/MyPackage@1.0.0", strict=True) + case_warnings = [m for m in messages if "lowercased" in m.message] + assert not case_warnings diff --git a/tests/test_purl_spec.py b/tests/test_purl_spec.py index a78d23d..4e3cc2c 100644 --- a/tests/test_purl_spec.py +++ b/tests/test_purl_spec.py @@ -178,6 +178,11 @@ def run_test_case(case: PurlTestCase): test_group = case.test_group if test_group not in ("base", "advanced"): raise Exception(test_group) + if test_group == "base" and case.description == "npm package with uppercase name": + # The pinned spec test data still expects npm names to be + # lowercased, but npm names are case-sensitive now. + # Drop this once the spec submodule is bumped. + pytest.xfail("stale pinned spec expectation: npm names are case-sensitive") strict = test_group == "base" messages = PackageURL.validate_string(purl=case.input, strict=strict) messages = [message.to_dict() for message in messages]