Skip to content

Commit

Permalink
Change: Store CPE attributes unquoted (e.g. version "1.2" instead of …
Browse files Browse the repository at this point in the history
…"1\.2")

When parsing a CPE and accessing its attributes the returned values are
now unquoted instead of being quoted according to the WNF. Using the
unquoted value allows to use them directly now.
  • Loading branch information
bjoernricks authored and greenbonebot committed Nov 8, 2023
1 parent f3723d0 commit 2bb3784
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 63 deletions.
172 changes: 127 additions & 45 deletions pontos/cpe/_cpe.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,60 @@ def split_cpe(cpe: str) -> list[str]:
return parts


@dataclass(frozen=True) # should require keywords only with Python >= 3.10
@dataclass(frozen=True)
class CPEWellFormed:
"""
Represents a Common Platform Enumeration (CPE) name using the Well-Formed
CPE Name (WNF) Data Model. Attributes are quoted according to the WNF model.
In most cases this class should not be used directly and the CPE class
should be used instead.
Attributes:
part: Value should be "a" for application, "o" for operating system or
"h" for hardware
vendor: Person or organization that manufactured or created the product
product: Identifies the most common and recognizable title or name of
the product
version: A vendor-specific alphanumeric string characterizing the
particular release version of the product
update: A vendor-specific alphanumeric string characterizing the
particular update, service pack, or point release of the product
edition: The edition attribute is considered deprecated in the 2.3
CPE specification, and it should be assigned the logical value ANY
except where required for backward compatibility with version 2.2 of
the CPE specification. This attribute is referred to as the “legacy
edition” attribute
language: Defines the language supported in the user interface of the
product (as language tags defined by RFC5646)
sw_edition: Characterizes how the product is tailored to a particular
market or class of end users. Extended attribute introduced with
version 2.3 of the CPE specification
target_sw: Characterizes the software computing environment within which
the product operates. Extended attribute introduced with
version 2.3 of the CPE specification
hardware_sw: Characterizes the instruction set architecture (e.g., x86)
on which the product operates. Extended attribute introduced with
version 2.3 of the CPE specification
other: Captures any other general descriptive or identifying information
which is vendor- or product-specific and which does not logically
fit in any other attribute value. Extended attribute introduced with
version 2.3 of the CPE specification
"""

part: Part
vendor: str
product: str
version: Optional[str] = None
update: Optional[str] = None
edition: Optional[str] = None
language: Optional[str] = None
sw_edition: Optional[str] = None
target_sw: Optional[str] = None
target_hw: Optional[str] = None
other: Optional[str] = None


class CPE:
"""
Represents a Common Platform Enumeration (CPE) name
Expand Down Expand Up @@ -433,22 +486,51 @@ class CPE:
print(cpe.vendor) # google
print(cpe.product) # android
print(cpe.version) # 13\.0
print(cpe.version) # 13.0
print(cpe.as_uri_binding()) # cpe:/o:google:android:13.0
"""

part: Part
vendor: str
product: str
version: Optional[str] = None
update: Optional[str] = None
edition: Optional[str] = None
language: Optional[str] = None
sw_edition: Optional[str] = None
target_sw: Optional[str] = None
target_hw: Optional[str] = None
other: Optional[str] = None
cpe_string: Optional[str] = None
def __init__(
self,
*,
cpe_string: Optional[str] = None,
part: Part,
vendor: str,
product: str,
version: Optional[str] = None,
update: Optional[str] = None,
edition: Optional[str] = None,
language: Optional[str] = None,
sw_edition: Optional[str] = None,
target_sw: Optional[str] = None,
target_hw: Optional[str] = None,
other: Optional[str] = None,
) -> None:
self.cpe_string = cpe_string
self.__wnf__ = CPEWellFormed(
part=part,
vendor=vendor,
product=product,
version=version,
update=update,
edition=edition,
language=language,
sw_edition=sw_edition,
target_sw=target_sw,
target_hw=target_hw,
other=other,
)
self.part = part
self.vendor = unquote_attribute_value(vendor)
self.product = unquote_attribute_value(product)
self.version = unquote_attribute_value(version)
self.update = unquote_attribute_value(update)
self.edition = unquote_attribute_value(edition)
self.language = unquote_attribute_value(language)
self.sw_edition = unquote_attribute_value(sw_edition)
self.target_sw = unquote_attribute_value(target_sw)
self.target_hw = unquote_attribute_value(target_hw)
self.other = unquote_attribute_value(other)

@staticmethod
def from_string(cpe: str) -> "CPE":
Expand Down Expand Up @@ -549,16 +631,16 @@ def as_uri_binding(self) -> str:
Converts the CPE to an URI binding
"""
part = self.part.value
vendor = bind_value_for_uri(self.vendor)
product = bind_value_for_uri(self.product)
version = bind_value_for_uri(self.version)
update = bind_value_for_uri(self.update)
language = bind_value_for_uri(self.language)
edition = bind_value_for_uri(self.edition)
sw_edition = bind_value_for_uri(self.sw_edition)
target_sw = bind_value_for_uri(self.target_sw)
target_hw = bind_value_for_uri(self.target_hw)
other = bind_value_for_uri(self.other)
vendor = bind_value_for_uri(self.__wnf__.vendor)
product = bind_value_for_uri(self.__wnf__.product)
version = bind_value_for_uri(self.__wnf__.version)
update = bind_value_for_uri(self.__wnf__.update)
language = bind_value_for_uri(self.__wnf__.language)
edition = bind_value_for_uri(self.__wnf__.edition)
sw_edition = bind_value_for_uri(self.__wnf__.sw_edition)
target_sw = bind_value_for_uri(self.__wnf__.target_sw)
target_hw = bind_value_for_uri(self.__wnf__.target_hw)
other = bind_value_for_uri(self.__wnf__.other)

edition = pack_extended_attributes(
edition,
Expand All @@ -584,16 +666,16 @@ def as_formatted_string_binding(self) -> str:
Converts the CPE to a formatted string binding
"""
part = self.part.value
vendor = bind_value_for_formatted_string(self.vendor)
product = bind_value_for_formatted_string(self.product)
version = bind_value_for_formatted_string(self.version)
update = bind_value_for_formatted_string(self.update)
edition = bind_value_for_formatted_string(self.edition)
language = bind_value_for_formatted_string(self.language)
sw_edition = bind_value_for_formatted_string(self.sw_edition)
target_sw = bind_value_for_formatted_string(self.target_sw)
target_hw = bind_value_for_formatted_string(self.target_hw)
other = bind_value_for_formatted_string(self.other)
vendor = bind_value_for_formatted_string(self.__wnf__.vendor)
product = bind_value_for_formatted_string(self.__wnf__.product)
version = bind_value_for_formatted_string(self.__wnf__.version)
update = bind_value_for_formatted_string(self.__wnf__.update)
edition = bind_value_for_formatted_string(self.__wnf__.edition)
language = bind_value_for_formatted_string(self.__wnf__.language)
sw_edition = bind_value_for_formatted_string(self.__wnf__.sw_edition)
target_sw = bind_value_for_formatted_string(self.__wnf__.target_sw)
target_hw = bind_value_for_formatted_string(self.__wnf__.target_hw)
other = bind_value_for_formatted_string(self.__wnf__.other)
return (
f"cpe:2.3:{part}:{vendor}:{product}:{version}:{update}:"
f"{edition}:{language}:{sw_edition}:{target_sw}:{target_hw}:{other}"
Expand All @@ -617,17 +699,17 @@ def clone(
all_android_versions = cpe.clone(version=ANY)
"""
args = {
"part": self.part,
"vendor": self.vendor,
"product": self.product,
"version": self.version,
"update": self.update,
"edition": self.edition,
"language": self.language,
"sw_edition": self.sw_edition,
"target_sw": self.target_sw,
"target_hw": self.target_hw,
"other": self.other,
"part": self.__wnf__.part,
"vendor": self.__wnf__.vendor,
"product": self.__wnf__.product,
"version": self.__wnf__.version,
"update": self.__wnf__.update,
"edition": self.__wnf__.edition,
"language": self.__wnf__.language,
"sw_edition": self.__wnf__.sw_edition,
"target_sw": self.__wnf__.target_sw,
"target_hw": self.__wnf__.target_hw,
"other": self.__wnf__.other,
"cpe_string": self.cpe_string,
}
args.update(**kwargs)
Expand Down
36 changes: 18 additions & 18 deletions tests/cpe/test_cpe.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ def test_uri_binding(self):
self.assertTrue(cpe.is_uri_binding())
self.assertFalse(cpe.is_formatted_string_binding())
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "foo\\\\bar")
self.assertEqual(cpe.product, "big\$money_manager_2010")
self.assertEqual(cpe.vendor, "foo\\bar")
self.assertEqual(cpe.product, "big$money_manager_2010")
self.assertEqual(cpe.version, ANY)
self.assertEqual(cpe.update, ANY)
self.assertIsNone(cpe.language)
Expand Down Expand Up @@ -358,7 +358,7 @@ def test_uri_unbind_examples(self):
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "microsoft")
self.assertEqual(cpe.product, "internet_explorer")
self.assertEqual(cpe.version, "8\.0\.6001")
self.assertEqual(cpe.version, "8.0.6001")
self.assertEqual(cpe.update, "beta")
self.assertIsNone(cpe.language)
self.assertIsNone(cpe.edition)
Expand All @@ -374,8 +374,8 @@ def test_uri_unbind_examples(self):
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "microsoft")
self.assertEqual(cpe.product, "internet_explorer")
self.assertEqual(cpe.version, "8\.\*")
self.assertEqual(cpe.update, "sp\?")
self.assertEqual(cpe.version, "8.\\*")
self.assertEqual(cpe.update, "sp\\?")
self.assertIsNone(cpe.language)
self.assertIsNone(cpe.edition)
self.assertIsNone(cpe.sw_edition)
Expand All @@ -390,7 +390,7 @@ def test_uri_unbind_examples(self):
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "microsoft")
self.assertEqual(cpe.product, "internet_explorer")
self.assertEqual(cpe.version, "8\.*")
self.assertEqual(cpe.version, "8.*")
self.assertEqual(cpe.update, "sp?")
self.assertIsNone(cpe.language)
self.assertIsNone(cpe.edition)
Expand All @@ -409,7 +409,7 @@ def test_uri_unbind_examples(self):
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "hp")
self.assertEqual(cpe.product, "insight_diagnostics")
self.assertEqual(cpe.version, "7\.4\.0\.1570")
self.assertEqual(cpe.version, "7.4.0.1570")
self.assertEqual(cpe.update, ANY)
self.assertIsNone(cpe.language)
self.assertIsNone(cpe.edition)
Expand All @@ -427,7 +427,7 @@ def test_uri_unbind_examples(self):
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "hp")
self.assertEqual(cpe.product, "openview_network_manager")
self.assertEqual(cpe.version, "7\.51")
self.assertEqual(cpe.version, "7.51")
self.assertEqual(cpe.update, NA)
self.assertIsNone(cpe.language)
self.assertIsNone(cpe.edition)
Expand All @@ -447,8 +447,8 @@ def test_uri_unbind_examples(self):
self.assertTrue(cpe.is_uri_binding())
self.assertFalse(cpe.is_formatted_string_binding())
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "foo\~bar")
self.assertEqual(cpe.product, "big\~money_2010")
self.assertEqual(cpe.vendor, "foo~bar")
self.assertEqual(cpe.product, "big~money_2010")
self.assertIsNone(cpe.version)
self.assertIsNone(cpe.update)
self.assertIsNone(cpe.language)
Expand Down Expand Up @@ -564,7 +564,7 @@ def test_formatted_unbind_examples(self):
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "microsoft")
self.assertEqual(cpe.product, "internet_explorer")
self.assertEqual(cpe.version, "8\.0\.6001")
self.assertEqual(cpe.version, "8.0.6001")
self.assertEqual(cpe.update, "beta")
self.assertEqual(cpe.edition, ANY)
self.assertEqual(cpe.language, ANY)
Expand All @@ -582,7 +582,7 @@ def test_formatted_unbind_examples(self):
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "microsoft")
self.assertEqual(cpe.product, "internet_explorer")
self.assertEqual(cpe.version, "8\.*")
self.assertEqual(cpe.version, "8.*")
self.assertEqual(cpe.update, "sp?")
self.assertEqual(cpe.language, ANY)
self.assertEqual(cpe.edition, ANY)
Expand All @@ -600,7 +600,7 @@ def test_formatted_unbind_examples(self):
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "hp")
self.assertEqual(cpe.product, "insight_diagnostics")
self.assertEqual(cpe.version, "7\.4\.0\.1570")
self.assertEqual(cpe.version, "7.4.0.1570")
self.assertEqual(cpe.update, NA)
self.assertEqual(cpe.language, ANY)
self.assertEqual(cpe.edition, ANY)
Expand All @@ -626,8 +626,8 @@ def test_formatted_unbind_examples(self):
self.assertFalse(cpe.is_uri_binding())
self.assertTrue(cpe.is_formatted_string_binding())
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "foo\\\\bar")
self.assertEqual(cpe.product, "big\$money")
self.assertEqual(cpe.vendor, "foo\\bar")
self.assertEqual(cpe.product, "big$money")
self.assertEqual(cpe.version, "2010")
self.assertEqual(cpe.update, ANY)
self.assertEqual(cpe.edition, ANY)
Expand All @@ -642,8 +642,8 @@ def test_formatted_unbind_examples(self):
self.assertTrue(cpe.is_formatted_string_binding())
self.assertEqual(cpe.part, Part.APPLICATION)
self.assertEqual(cpe.vendor, "foo")
self.assertEqual(cpe.product, "bar\:mumble")
self.assertEqual(cpe.version, "1\.0")
self.assertEqual(cpe.product, "bar:mumble")
self.assertEqual(cpe.version, "1.0")
self.assertEqual(cpe.update, ANY)
self.assertEqual(cpe.edition, ANY)
self.assertEqual(cpe.language, ANY)
Expand Down Expand Up @@ -727,5 +727,5 @@ def test_clone(self):
)
cpe2 = cpe.clone(version=ANY)
self.assertIsNot(cpe, cpe2)
self.assertEqual(cpe.version, "7\\.51")
self.assertEqual(cpe.version, "7.51")
self.assertEqual(cpe2.version, ANY)

0 comments on commit 2bb3784

Please sign in to comment.