Skip to content
90 changes: 90 additions & 0 deletions vulnerabilities/importers/curl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# VulnerableCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/nexB/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#

import logging
Comment thread
pombredanne marked this conversation as resolved.
from datetime import datetime
from datetime import timezone
from typing import Iterable
from typing import Mapping

import requests
from packageurl import PackageURL
from univers.version_range import GenericVersionRange
from univers.versions import SemverVersion
from utils import fetch_response
from utils import get_item

from vulnerabilities.importer import AdvisoryData
from vulnerabilities.importer import AffectedPackage
from vulnerabilities.importer import Importer
from vulnerabilities.importer import Reference
from vulnerabilities.importer import VulnerabilitySeverity
from vulnerabilities.severity_systems import SCORING_SYSTEMS

logger = logging.getLogger(__name__)


class CurlImporter(Importer):

spdx_license_expression = "MIT"
license_url = "https://github.com/curl/curl-www/blob/master/LICENSE"
repo_url = "https://github.com/curl/curl-www/"
importer_name = "Curl Importer"
api_url = "https://curl.se/docs/vuln.json"

def fetch(self) -> Iterable[Mapping]:
response = fetch_response(self.url)
return response.json()

def advisory_data(self) -> Iterable[AdvisoryData]:
raw_data = self.fetch()
for data in raw_data:
cve_id = data.get("aliases") or []
cve_id = cve_id[0] if len(cve_id) > 0 else None
if not cve_id.startswith("CVE"):
package = data.get("database_specific").get("package")
logger.error(f"Invalid CVE ID: {cve_id} in package {package}")
continue
yield parse_advisory_data(data)


def parse_advisory_data(raw_data) -> AdvisoryData:

d1 = get_item(raw_data, "affected")[0] if len(get_item(raw_data, "affected")) > 0 else []
d2 = get_item(d1, "ranges")[0] if len(get_item(d1, "ranges")) > 0 else []
d3 = get_item(d2, "events")[1] if len(get_item(d2, "events")) > 1 else {}

fixed_version = SemverVersion(d3.get("fixed") or "")
purl = PackageURL(type="generic", namespace="curl.se", name="curl")
affected_version_range = GenericVersionRange.from_versions(
raw_data.get("affected")[0].get("versions") or []
)

affected_package = AffectedPackage(
package=purl, affected_version_range=affected_version_range, fixed_version=fixed_version
)

database_specific = raw_data.get("database_specific") or {}
severity = VulnerabilitySeverity(
system=SCORING_SYSTEMS["generic_textual"], value=database_specific.get("severity", "")
)

references = [Reference(url=database_specific.get("www") or "", severities=[severity])]
date_published = datetime.strptime(raw_data.get("published") or "", "%d-%m-%Y %Z").replace(
tzinfo=timezone.utc
)

return AdvisoryData(
aliases=raw_data.get("aliases") or [],
summary=raw_data.get("summary") or "",
affected_packages=[affected_package],
references=references,
date_published=date_published,
url=raw_data.get("database_specific", {}).get("URL", ""),
)
53 changes: 53 additions & 0 deletions vulnerabilities/tests/test_curl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# VulnerableCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/nexB/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#

import json
import os
from unittest.mock import patch

import requests
from bs4 import BeautifulSoup

# from importer import AdvisoryData
from univers.versions import SemverVersion

from vulnerabilities.importers import curl
from vulnerabilities.tests import util_tests

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TEST_DATA = os.path.join(BASE_DIR, "test_data/curl")


def test_is_semver_version():
url = "https://curl.se/docs/releases.html"

response = requests.get(url)

if response.status_code == 200:
soup = BeautifulSoup(response.text, "html.parser")
rows = soup.find_all("tr")
versions = []
for row in rows:

cells = row.find_all("td")
if len(cells) >= 2:
version = cells[1].get_text().strip()
versions.append(version)
c = 0
for version in versions:
semver_version = SemverVersion(version)
if semver_version.is_valid(version):
c += 1
if c == len(versions):
print("All versions of curl are SemVer versions.")
else:
print("Not all versions are SemVer versions.")
else:
print("Failed to retrieve data from the webpage")
2 changes: 1 addition & 1 deletion vulntotal/tests/test_osv.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@ def test_generate_payload_nuget_with_api_call(self):
purl = PackageURL.from_string("pkg:nuget/moment.js@2.18.0")
results = osv.generate_payload(purl)
expected = {"package": {"ecosystem": "NuGet", "name": "Moment.js"}, "version": "2.18.0"}
assert results == expected
assert results == expected
Comment thread
ambuj-1211 marked this conversation as resolved.