diff --git a/osv/ecosystems/alpine.py b/osv/ecosystems/alpine.py index 5ee871712bf..766a81058aa 100644 --- a/osv/ecosystems/alpine.py +++ b/osv/ecosystems/alpine.py @@ -53,6 +53,10 @@ def get_branch_name(self) -> str: return self.alpine_release_ver.lstrip('v') + self._BRANCH_SUFFIX def sort_key(self, version): + if not AlpineLinuxVersion.is_valid(version): + # If version is not valid, it is most likely an invalid input + # version then sort it to the last/largest element + return AlpineLinuxVersion('999999') return AlpineLinuxVersion(version) @staticmethod diff --git a/osv/ecosystems/alpine_test.py b/osv/ecosystems/alpine_test.py index 1f193719880..2f7aedf34ab 100644 --- a/osv/ecosystems/alpine_test.py +++ b/osv/ecosystems/alpine_test.py @@ -61,4 +61,8 @@ def test_alpine(self, ensure_updated_checkout_mock: mock.MagicMock): self.assertGreater( ecosystem.sort_key('1.13.2-r0'), ecosystem.sort_key('1.13.2_alpha')) + # Check invalid version handle + self.assertGreater( + ecosystem.sort_key('1-0-0'), ecosystem.sort_key('1.13.2-r0')) + ecosystems.config.set_cache(None) diff --git a/osv/ecosystems/bioconductor.py b/osv/ecosystems/bioconductor.py index fb58cdd2df0..2dafd63d5c4 100644 --- a/osv/ecosystems/bioconductor.py +++ b/osv/ecosystems/bioconductor.py @@ -44,6 +44,10 @@ def get_bioc_versions(self): def sort_key(self, version): """Sort key.""" + if not semver_index.is_valid(version): + # If version is not valid, it is most likely an invalid input + # version then sort it to the last/largest element + return semver_index.parse('999999') return semver_index.parse(version) def _enumerate_versions(self, diff --git a/osv/ecosystems/bioconductor_test.py b/osv/ecosystems/bioconductor_test.py index 4484c867e52..1b6235a647e 100644 --- a/osv/ecosystems/bioconductor_test.py +++ b/osv/ecosystems/bioconductor_test.py @@ -26,5 +26,6 @@ def test_next_version(self): ecosystem = ecosystems.get('Bioconductor') self.assertEqual('1.18.0', ecosystem.next_version('a4', '1.16.0')) self.assertEqual('1.20.0', ecosystem.next_version('a4', '1.18.0')) + self.assertGreater(ecosystem.sort_key('1-0'), ecosystem.sort_key('1.2.0')) with self.assertRaises(ecosystems.EnumerateError): ecosystem.next_version('doesnotexist123456', '1') diff --git a/osv/ecosystems/cran.py b/osv/ecosystems/cran.py index cb446135e21..782577203b9 100644 --- a/osv/ecosystems/cran.py +++ b/osv/ecosystems/cran.py @@ -37,6 +37,7 @@ def sort_key(self, version): # The packaging.version appears to work for the typical X.Y.Z and # X.Y-Z cases version = version.replace("-", ".") + # version.parse() handles invalid versions by returning LegacyVersion() return packaging.version.parse(version) def _enumerate_versions(self, diff --git a/osv/ecosystems/haskell.py b/osv/ecosystems/haskell.py index 8871a7864fe..569bd21933a 100644 --- a/osv/ecosystems/haskell.py +++ b/osv/ecosystems/haskell.py @@ -40,7 +40,12 @@ def sort_key(self, version): https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-Version.html """ - return [int(x) for x in version.split('.')] + # If version is not valid, it is most likely an invalid input version + # then sort it to the last/largest element + try: + return [int(x) for x in version.split('.')] + except ValueError: + return [999999] def enumerate_versions(self, package, diff --git a/osv/ecosystems/haskell_test.py b/osv/ecosystems/haskell_test.py index a4e68623bb3..9e8ad3137c8 100644 --- a/osv/ecosystems/haskell_test.py +++ b/osv/ecosystems/haskell_test.py @@ -31,6 +31,12 @@ def test_next_version(self): with self.assertRaises(ecosystems.EnumerateError): ecosystem.next_version('doesnotexist123456', '1') + def test_sort_key(self): + """Test sort_key.""" + ecosystem = ecosystems.get('Hackage') + self.assertGreater( + ecosystem.sort_key('1-20-0'), ecosystem.sort_key('1.20.0')) + class GHCEcosystemTest(unittest.TestCase): """GHC ecosystem helper tests.""" diff --git a/osv/ecosystems/maven_test.py b/osv/ecosystems/maven_test.py index 5492a0ee6fb..6de7b8e4336 100644 --- a/osv/ecosystems/maven_test.py +++ b/osv/ecosystems/maven_test.py @@ -80,32 +80,10 @@ def test_sort(self): """Basic sort tests.""" # These tests are taken from the spec. unsorted = [ - '1', - '1.1', - '1-snapshot', - '1', - '1-sp', - '1-foo2', - '1-foo10', - '1.foo', - '1-foo', - '1-1', - '1.1', - '1.ga', - '1-ga', - '1-0', - '1.0', - '1', - '1-sp', - '1-ga', - '1-sp.1', - '1-ga.1', - '1-sp-1', - '1-ga-1', - '1-1', - '1-a1', - '1-alpha-1', - '2', + '1', '1.1', '1-snapshot', '1', '1-sp', '1-foo2', '1-foo10', '1.foo', + '1-foo', '1-1', '1.1', '1.ga', '1-ga', '1-0', '1.0', '1', '1-sp', + '1-ga', '1-sp.1', '1-ga.1', '1-sp-1', '1-ga-1', '1-1', '1-a1', + '1-alpha-1', '2', 'invalid', '0' ] sorted_versions = [ @@ -113,9 +91,10 @@ def test_sort(self): ] self.assertListEqual([ - '1-alpha-1', '1-alpha-1', '1-snapshot', '1', '1', '1', '1', '1', '1', - '1', '1', '1.foo', '1-.1', '1-sp', '1-sp', '1-sp-1', '1-sp.1', '1-foo', - '1-foo-2', '1-foo-10', '1-1', '1-1', '1-1', '1.1', '1.1', '2' + 'invalid', '0', '1-alpha-1', '1-alpha-1', '1-snapshot', '1', '1', '1', + '1', '1', '1', '1', '1', '1.foo', '1-.1', '1-sp', '1-sp', '1-sp-1', + '1-sp.1', '1-foo', '1-foo-2', '1-foo-10', '1-1', '1-1', '1-1', '1.1', + '1.1', '2' ], sorted_versions) def test_versions_qualifiers(self): diff --git a/osv/ecosystems/nuget.py b/osv/ecosystems/nuget.py index 86342c485e8..d95b79f46ec 100644 --- a/osv/ecosystems/nuget.py +++ b/osv/ecosystems/nuget.py @@ -72,6 +72,10 @@ def __lt__(self, other): def from_string(cls, str_version): str_version = semver_index.coerce(str_version) str_version, revision = _extract_revision(str_version) + if not semver_index.is_valid(str_version): + # If version is not valid, it is most likely an invalid input + # version then sort it to the last/largest element + return Version(semver_index.parse('999999'), 999999) return Version(semver_index.parse(str_version), revision) diff --git a/osv/ecosystems/nuget_test.py b/osv/ecosystems/nuget_test.py index 21ed0d1adea..bd4be2b3f6f 100644 --- a/osv/ecosystems/nuget_test.py +++ b/osv/ecosystems/nuget_test.py @@ -100,6 +100,12 @@ def test_next_version(self): with self.assertRaises(ecosystems.EnumerateError): ecosystem.next_version('doesnotexist123456', '1') + def test_sort_key(self): + ecosystem = ecosystems.get('NuGet') + # Tests invalid input versions + self.assertGreater( + ecosystem.sort_key('1.4.0rc3'), ecosystem.sort_key('3.0.0.4001')) + if __name__ == '__main__': unittest.main() diff --git a/osv/ecosystems/packagist_test.py b/osv/ecosystems/packagist_test.py index 3a88ace8b04..26b19787bbc 100644 --- a/osv/ecosystems/packagist_test.py +++ b/osv/ecosystems/packagist_test.py @@ -27,6 +27,8 @@ class PackagistEcosystemTest(unittest.TestCase): def test_packagist(self): """Test Packagist.""" ecosystem = ecosystems.get('Packagist') + # Any invalid versions will be handled. + self.assertLess(ecosystem.sort_key('invalid'), ecosystem.sort_key('0')) self.assertLess( ecosystem.sort_key('4.3-2RC1'), ecosystem.sort_key('4.3-2RC2')) self.assertGreater( diff --git a/osv/ecosystems/pub.py b/osv/ecosystems/pub.py index 7bbbca421ca..01f1fdbebe6 100644 --- a/osv/ecosystems/pub.py +++ b/osv/ecosystems/pub.py @@ -58,7 +58,12 @@ def __eq__(self, other): @classmethod def from_string(cls, str_version): - return Version(semver_index.parse(str_version)) + # If version is not valid, it is most likely an invalid input + # version then sort it to the last/largest element + try: + return Version(semver_index.parse(str_version)) + except ValueError: + return Version(semver_index.parse('999999')) class Pub(Ecosystem): diff --git a/osv/ecosystems/pub_test.py b/osv/ecosystems/pub_test.py index 796540e2c4f..84f20a95e78 100644 --- a/osv/ecosystems/pub_test.py +++ b/osv/ecosystems/pub_test.py @@ -66,6 +66,8 @@ def test_parse(self): pub.Version.from_string('1.2.3+build.1') pub.Version.from_string('1.2.3+x.7.z-92') pub.Version.from_string('1.0.0-rc-1+build-1') + # Tests invalid versions + pub.Version.from_string('3.4.0rc3-invalid') def test_empty_identifier(self): """Test parsing versions with empty identifiers. diff --git a/osv/ecosystems/pypi.py b/osv/ecosystems/pypi.py index ab432651941..f1283b0bd90 100644 --- a/osv/ecosystems/pypi.py +++ b/osv/ecosystems/pypi.py @@ -27,6 +27,7 @@ class PyPI(Ecosystem): def sort_key(self, version): """Sort key.""" + # version.parse() handles invalid versions by returning LegacyVersion() return packaging.version.parse(version) def enumerate_versions(self, diff --git a/osv/ecosystems/pypi_test.py b/osv/ecosystems/pypi_test.py index 3a43f816384..684938716e4 100644 --- a/osv/ecosystems/pypi_test.py +++ b/osv/ecosystems/pypi_test.py @@ -29,3 +29,9 @@ def test_next_version(self): self.assertEqual('0.3.0', ecosystem.next_version('grpcio', '0')) with self.assertRaises(ecosystems.EnumerateError): ecosystem.next_version('doesnotexist123456', '1') + + def test_sort_key(self): + """Test sort_key""" + ecosystem = ecosystems.get('PyPI') + self.assertGreater(ecosystem.sort_key('2.0.0'), ecosystem.sort_key('1.0.0')) + self.assertLess(ecosystem.sort_key('invalid'), ecosystem.sort_key('0')) diff --git a/osv/ecosystems/rocky_linux_test.py b/osv/ecosystems/rocky_linux_test.py index 831eb6d9d92..b54c9f95c36 100644 --- a/osv/ecosystems/rocky_linux_test.py +++ b/osv/ecosystems/rocky_linux_test.py @@ -21,14 +21,16 @@ class RockyLinuxEcosystemTest(unittest.TestCase): """Rocky Linux ecosystem helper tests.""" def test_rocky_linux(self): + """Test sort_key""" ecosystem = ecosystems.get('Rocky Linux') - self.assertEqual("Rocky Linux", ecosystem.name) + self.assertEqual('Rocky Linux', ecosystem.name) self.assertGreater( - ecosystem.sort_key("0:0.2.6-20.module+el8.9.0+1420+91577025"), - ecosystem.sort_key("0:0.0.99.4-5.module+el8.9.0+1445+07728297")) + ecosystem.sort_key('0:0.2.6-20.module+el8.9.0+1420+91577025'), + ecosystem.sort_key('0:0.0.99.4-5.module+el8.9.0+1445+07728297')) self.assertGreater( - ecosystem.sort_key("0:0.2.6-20.module+el8.9.0+1420+91577025"), - ecosystem.sort_key("0")) + ecosystem.sort_key('0:0.2.6-20.module+el8.9.0+1420+91577025'), + ecosystem.sort_key('0')) self.assertGreater( - ecosystem.sort_key("2:1.14.3-2.module+el8.10.0+1815+5fe7415e"), - ecosystem.sort_key("2:1.10.3-1.module+el8.10.0+1815+5fe7415e")) + ecosystem.sort_key('2:1.14.3-2.module+el8.10.0+1815+5fe7415e'), + ecosystem.sort_key('2:1.10.3-1.module+el8.10.0+1815+5fe7415e')) + self.assertLess(ecosystem.sort_key('invalid'), ecosystem.sort_key('0')) diff --git a/osv/ecosystems/rubygems.py b/osv/ecosystems/rubygems.py index e76743b8631..38e528917b4 100644 --- a/osv/ecosystems/rubygems.py +++ b/osv/ecosystems/rubygems.py @@ -15,7 +15,7 @@ import requests -from ..third_party.univers.gem import GemVersion +from ..third_party.univers.gem import GemVersion, InvalidVersionError from . import config from .helper_base import Ecosystem, EnumerateError @@ -28,7 +28,12 @@ class RubyGems(Ecosystem): def sort_key(self, version): """Sort key.""" - return GemVersion(version) + # If version is not valid, it is most likely an invalid input + # version then sort it to the last/largest element + try: + return GemVersion(version) + except InvalidVersionError: + return GemVersion('999999') def enumerate_versions(self, package, diff --git a/osv/ecosystems/rubygems_test.py b/osv/ecosystems/rubygems_test.py index fee3cd524b1..22ddad307ad 100644 --- a/osv/ecosystems/rubygems_test.py +++ b/osv/ecosystems/rubygems_test.py @@ -33,3 +33,11 @@ def test_next_version(self): ecosystem.next_version('rails', '5.0.0.beta4')) with self.assertRaises(ecosystems.EnumerateError): ecosystem.next_version('doesnotexist123456', '1') + + def test_sort_key(self): + """Test sort_key with invalid versions""" + ecosystem = ecosystems.get('RubyGems') + self.assertGreater( + ecosystem.sort_key('invalid'), ecosystem.sort_key('4.0.0.rc1')) + self.assertGreater( + ecosystem.sort_key('v3.1.1'), ecosystem.sort_key('4.0.0.rc1')) diff --git a/osv/ecosystems/ubuntu_test.py b/osv/ecosystems/ubuntu_test.py index f8c0c1d0171..8224319e1eb 100644 --- a/osv/ecosystems/ubuntu_test.py +++ b/osv/ecosystems/ubuntu_test.py @@ -21,13 +21,16 @@ class UbuntuEcosystemTest(unittest.TestCase): """Ubuntu ecosystem helper tests.""" def test_ubuntu(self): + """Test sort_key""" ecosystem = ecosystems.get('Ubuntu') self.assertGreater( - ecosystem.sort_key("2.42.8+dfsg-1ubuntu0.3"), - ecosystem.sort_key("2.40.0+dfsg-3ubuntu0.5")) + ecosystem.sort_key('2.42.8+dfsg-1ubuntu0.3'), + ecosystem.sort_key('2.40.0+dfsg-3ubuntu0.5')) self.assertGreater( - ecosystem.sort_key("2.42.8+dfsg-1ubuntu0.3"), - ecosystem.sort_key("2.42.8+dfsg-1ubuntu0.2")) - self.assertGreater(ecosystem.sort_key("5.4.13-1"), ecosystem.sort_key("0")) + ecosystem.sort_key('2.42.8+dfsg-1ubuntu0.3'), + ecosystem.sort_key('2.42.8+dfsg-1ubuntu0.2')) + self.assertGreater(ecosystem.sort_key('5.4.13-1'), ecosystem.sort_key('0')) self.assertGreater( - ecosystem.sort_key("5.4.13-1"), ecosystem.sort_key("3.2.30-1")) + ecosystem.sort_key('5.4.13-1'), ecosystem.sort_key('3.2.30-1')) + self.assertGreater( + ecosystem.sort_key('invalid'), ecosystem.sort_key('3.2.30-1'))