Skip to content
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
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Version v31.1.0

- We re-enabled support for the NPM vulnerabilities advisories importer.
- We re-enabled support for the Retiredotnet vulnerabilities advisories importer.
- We are now handling purl fragments in package search. For example:
you can now serch using queries in the UI like this : `cherrypy@2.1.1`,
`cherrypy` or `pkg:pypi`.


Version v31.0.0
Expand Down
55 changes: 8 additions & 47 deletions vulnerabilities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,61 +412,22 @@ def search(self, query=None):
query = query and query.strip()
if not query:
return self.none()

qs = self
if not query.startswith("pkg:"):
# treat this as a plain search
qs = qs.filter(Q(name__icontains=query) | Q(namespace__icontains=query))
else:
# this looks like a purl: check if it quacks like a purl
purl_type = namespace = name = version = None

_, _scheme, remainder = query.partition("pkg:")
remainder = remainder.strip()
if not remainder:
return qs.none()

try:
# First, treat the query as a syntactically-correct purl
purl = PackageURL.from_string(query)
purl_type, namespace, name, version, _quals, _subp = purl.to_dict().values()
except ValueError:
# Otherwise, attempt a more lenient parsing of a possibly partial purl
if "/" in remainder:
purl_type, _scheme, ns_name = remainder.partition("/")
ns_name = ns_name.strip()
if ns_name:
if "/" in ns_name:
namespace, _, name = ns_name.partition("/")
else:
name = ns_name
name = name.strip()
if name:
if "@" in name:
name, _, version = name.partition("@")
version = version.strip()
name = name.strip()
else:
purl_type = remainder

if purl_type:
qs = qs.filter(type__iexact=purl_type)
if namespace:
qs = qs.filter(namespace__iexact=namespace)
if name:
qs = qs.filter(name__iexact=name)
if version:
qs = qs.filter(version__iexact=version)

return qs

try:
# if it's a valid purl, use it as is
purl = PackageURL.from_string(query)
return self.for_purl(purl, with_qualifiers_and_subpath=False)
except ValueError:
return qs.filter(package_url__icontains=query)

def for_purl(self, purl, with_qualifiers_and_subpath=True):
"""
Return a queryset matching the ``purl`` Package URL.
"""
if not isinstance(purl, PackageURL):
purl = PackageURL.from_string(purl)
purl = purl.to_dict()
purl = purl_to_dict(purl)
if not with_qualifiers_and_subpath:
del purl["qualifiers"]
del purl["subpath"]
Expand Down
6 changes: 6 additions & 0 deletions vulnerabilities/tests/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ def test_package_view(self):
self.assertEqual(len(pkgs), 1)
self.assertEqual(pkgs[0].purl, "pkg:nginx/nginx@1.0.15")

def test_package_view_with_purl_fragment(self):
qs = PackageSearch().get_queryset(query="nginx@1.0.15")
pkgs = list(qs)
self.assertEqual(len(pkgs), 1)
self.assertEqual(pkgs[0].purl, "pkg:nginx/nginx@1.0.15")


class VulnerabilitySearchTestCase(TestCase):
def setUp(self):
Expand Down