diff --git a/src/packageurl/validate.py b/src/packageurl/validate.py index 4ee81c3..e6e4193 100644 --- a/src/packageurl/validate.py +++ b/src/packageurl/validate.py @@ -142,17 +142,31 @@ def _validate_namespace(cls, purl): message=f"Namespace is not lowercased for purl type: {cls.type!r}", ) + if purl.namespace and any( + segment in (".", "..") for segment in purl.namespace.split("/") + ): + yield ValidationMessage( + severity=ValidationSeverity.ERROR, + message=f"Namespace contains a '.' or '..' path segment: {purl.namespace!r}", + ) + @classmethod def _validate_name(cls, purl): - if not cls.name_case_sensitive and purl.name and purl.name.lower() != purl.name: - from packageurl import ValidationMessage - from packageurl import ValidationSeverity + from packageurl import ValidationMessage + from packageurl import ValidationSeverity + if not cls.name_case_sensitive and purl.name and purl.name.lower() != purl.name: yield ValidationMessage( severity=ValidationSeverity.WARNING, message=f"Name is not lowercased for purl type: {cls.type!r}", ) + if purl.name in (".", ".."): + yield ValidationMessage( + severity=ValidationSeverity.ERROR, + message=f"Name cannot be a '.' or '..' path segment: {purl.name!r}", + ) + @classmethod def _validate_version(cls, purl): if not cls.version_case_sensitive and purl.version and purl.version.lower() != purl.version: diff --git a/tests/test_packageurl.py b/tests/test_packageurl.py index 2c36549..ce6af8e 100644 --- a/tests/test_packageurl.py +++ b/tests/test_packageurl.py @@ -388,3 +388,40 @@ 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_validate_string_reports_dot_segments_in_namespace(): + # https://github.com/package-url/packageurl-python/issues/228 + from packageurl import PackageURL + from packageurl import ValidationSeverity + + messages = PackageURL.validate_string("pkg:golang/busybox.org/../busybox@1.35.0") + assert any( + m.severity == ValidationSeverity.ERROR and "'.' or '..' path segment" in m.message + for m in messages + ) + + messages = PackageURL.validate_string("pkg:golang/busybox.org/./busybox@1.35.0") + assert any( + m.severity == ValidationSeverity.ERROR and "'.' or '..' path segment" in m.message + for m in messages + ) + + +def test_validate_string_reports_dot_segments_as_name(): + from packageurl import PackageURL + from packageurl import ValidationSeverity + + messages = PackageURL.validate_string("pkg:golang/busybox.org/..@1.35.0") + assert any( + m.severity == ValidationSeverity.ERROR and "'.' or '..'" in m.message + for m in messages + ) + + +def test_validate_string_accepts_dotted_names_and_namespaces(): + from packageurl import PackageURL + + assert not PackageURL.validate_string("pkg:golang/busybox.org/busybox@1.35.0") + # names and namespace segments that merely contain dots are fine + assert not PackageURL.validate_string("pkg:golang/example.com/my..pkg@1.0.0")