Skip to content

Commit

Permalink
Change: Improve CPE function names, doc strings and some tests
Browse files Browse the repository at this point in the history
Rename some CPE functions and extend their doc strings for explaining
their purpose even better. Add some additional tests for some functions
to ensure they are working as expected despite already being tested
implicitly.
  • Loading branch information
bjoernricks authored and greenbonebot committed Nov 8, 2023
1 parent a9dc39e commit be2b808
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 23 deletions.
49 changes: 28 additions & 21 deletions pontos/cpe/_cpe.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,23 @@ def is_formatted_string_binding(cpe: str) -> bool:
return cpe.startswith("cpe:2.3:")


def _remove_backslash(value: str) -> str:
def convert_double_backslash(value: str) -> str:
"""
Remove a single backslash
Convert a double backslash into s single backslash
"""
return re.sub("\\\\(\\W)", lambda match: match.group(1), value)


def _url_quote(value: str) -> str:
"""
Quote value according to the pct_encode function from the spec
Quote value according to the pct_encode function from the spec for uri format
"""
return urllib.parse.quote(value, safe="").lower()


def _url_unquote(value: str) -> str:
"""
Un-quote value according to the the spec
Un-quote value according to the the spec for uri format
"""
return urllib.parse.unquote(value)

Expand Down Expand Up @@ -121,7 +121,7 @@ def unpack_edition(edition: str) -> dict[str, Optional[str]]:
)


def bind_value_for_fs(value: Optional[str]) -> str:
def bind_value_for_formatted_string(value: Optional[str]) -> str:
"""
Convert an attribute value for formatted string representation
"""
Expand All @@ -136,27 +136,32 @@ def bind_value_for_fs(value: Optional[str]) -> str:

def _add_quoting(value: str) -> str:
"""
Add quoting for parsing attributes from formatted string format
Add quoting for parsing attributes from formatted string format to
Well-Formed CPE Name Data Model (WFN)
"""
result = ""
index = 0
embedded = False

while index < len(value):
c = value[index]
if c.isalnum() or c in ["_"]: # not sure about "-" and "~"
if c.isalnum() or c in ["_"]:
# just add character
result += c
index += 1
embedded = True
continue

if c == "\\":
# keep escaped character
result += value[index : index + 2]
index += 2
embedded = True
continue

if c == ANY:
# An unquoted asterisk must appear at the beginning or
# end of the string.
if index == 0 or index == (len(value) - 1):
result += c
index += 1
Expand All @@ -168,6 +173,8 @@ def _add_quoting(value: str) -> str:
f"of '{value}'"
)
if c == "?":
# An unquoted question mark must appear at the beginning or
# end of the string, or in a leading or trailing sequence
if (
( # ? is legal at the beginning or the end
(index == 0) or (index == (len(value) - 1))
Expand Down Expand Up @@ -197,9 +204,9 @@ def _add_quoting(value: str) -> str:
return result


def unbind_value_fs(value: Optional[str]) -> Optional[str]:
def unbind_value_from_formatted_string(value: Optional[str]) -> Optional[str]:
"""
Convert a formatted string representation to an attribute value
Convert a formatted string representation to an attribute value for WNF
"""
if value is None or value == ANY or value == NA:
return value
Expand Down Expand Up @@ -230,7 +237,7 @@ def _transform_for_uri(value: str) -> str:
if c == "\\":
index += 1
next = value[index]
transformed += _url_quote(_remove_backslash(next))
transformed += _url_quote(convert_double_backslash(next))
index += 1
continue

Expand Down Expand Up @@ -472,7 +479,7 @@ def from_string(cpe: str) -> "CPE":
"target_hw",
"other",
],
[unbind_value_fs(a) for a in parts[3:]],
[unbind_value_from_formatted_string(a) for a in parts[3:]],
)
)

Expand Down Expand Up @@ -547,16 +554,16 @@ def as_formatted_string_binding(self) -> str:
Converts the CPE to a formatted string binding
"""
part = self.part.value
vendor = bind_value_for_fs(self.vendor)
product = bind_value_for_fs(self.product)
version = bind_value_for_fs(self.version)
update = bind_value_for_fs(self.update)
edition = bind_value_for_fs(self.edition)
language = bind_value_for_fs(self.language)
sw_edition = bind_value_for_fs(self.sw_edition)
target_sw = bind_value_for_fs(self.target_sw)
target_hw = bind_value_for_fs(self.target_hw)
other = bind_value_for_fs(self.other)
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)
return (
f"cpe:2.3:{part}:{vendor}:{product}:{version}:{update}:"
f"{edition}:{language}:{sw_edition}:{target_sw}:{target_hw}:{other}"
Expand Down
105 changes: 103 additions & 2 deletions tests/cpe/test_cpe.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
import unittest

from pontos.cpe import ANY, CPE, NA, CPEParsingError, Part
from pontos.cpe._cpe import split_cpe
from pontos.cpe._cpe import (
bind_value_for_formatted_string,
convert_double_backslash,
split_cpe,
unbind_value_from_formatted_string,
)


class SplitCpeTestCase(unittest.TestCase):
Expand Down Expand Up @@ -61,6 +66,103 @@ def test_split_formatted_cpe(self):
self.assertEqual(parts[12], "*")


class ConvertDoubleBackslashTestCase(unittest.TestCase):
def test_remove_backslash(self):
self.assertEqual(convert_double_backslash("foo-bar"), "foo-bar")
self.assertEqual(convert_double_backslash("foo\\bar"), "foo\\bar")
self.assertEqual(convert_double_backslash("foo\\\\bar"), "foo\\bar")


class UnbindValueFromFormattedStringTestCase(unittest.TestCase):
def test_unchanged(self):
self.assertIsNone(unbind_value_from_formatted_string(None))
self.assertEqual(unbind_value_from_formatted_string(ANY), ANY)
self.assertEqual(unbind_value_from_formatted_string(NA), NA)

self.assertEqual(
unbind_value_from_formatted_string("foo_bar"), "foo_bar"
)
self.assertEqual(
unbind_value_from_formatted_string("foo\\:bar"), "foo\\:bar"
)
self.assertEqual(
unbind_value_from_formatted_string("1\\.2\\.3"), "1\\.2\\.3"
)

def test_quoting(self):
self.assertEqual(
unbind_value_from_formatted_string("foo-bar"), "foo\\-bar"
)
self.assertEqual( # not sure if this can happen and if it's valid
unbind_value_from_formatted_string("foo:bar"), "foo\\:bar"
)
self.assertEqual(
unbind_value_from_formatted_string("1.2.3"), "1\\.2\\.3"
)

def test_asterisk(self):
self.assertEqual(unbind_value_from_formatted_string("*foo"), "*foo")
self.assertEqual(unbind_value_from_formatted_string("foo*"), "foo*")
self.assertEqual(unbind_value_from_formatted_string("foo\\*"), "foo\\*")

with self.assertRaisesRegex(
CPEParsingError,
"An unquoted asterisk must appear at the beginning or end of "
"'foo\*bar'",
):
unbind_value_from_formatted_string("foo*bar")

with self.assertRaisesRegex(
CPEParsingError,
"An unquoted asterisk must appear at the beginning or end of "
"'\*\*foo'",
):
unbind_value_from_formatted_string("**foo")

def test_question_mark(self):
self.assertEqual(unbind_value_from_formatted_string("?foo"), "?foo")
self.assertEqual(unbind_value_from_formatted_string("??foo"), "??foo")
self.assertEqual(unbind_value_from_formatted_string("foo?"), "foo?")
self.assertEqual(unbind_value_from_formatted_string("foo??"), "foo??")
self.assertEqual(unbind_value_from_formatted_string("foo\\?"), "foo\\?")

with self.assertRaisesRegex(
CPEParsingError,
"An unquoted question mark must appear at the beginning or end, "
"or in a leading or trailing sequence 'foo\?bar'",
):
unbind_value_from_formatted_string("foo?bar")


class BindValueForFormattedStringTestCase(unittest.TestCase):
def test_any(self):
self.assertEqual(bind_value_for_formatted_string(None), ANY)
self.assertEqual(bind_value_for_formatted_string(""), ANY)
self.assertEqual(bind_value_for_formatted_string(ANY), ANY)

def test_na(self):
self.assertEqual(bind_value_for_formatted_string(NA), NA)

def test_remove_quoting(self):
self.assertEqual(bind_value_for_formatted_string("1\\.2\\.3"), "1.2.3")
# _ doesn't get quoted during unbinding therefore unquoting it here
# doesn't really make sense bit it's in the standard!
self.assertEqual(
bind_value_for_formatted_string("foo\\_bar"), "foo_bar"
)
self.assertEqual(
bind_value_for_formatted_string("foo\\-bar"), "foo-bar"
)

def test_unchanged(self):
self.assertEqual(
bind_value_for_formatted_string("foo\\:bar"), "foo\\:bar"
)
self.assertEqual(bind_value_for_formatted_string("?foo"), "?foo")
self.assertEqual(bind_value_for_formatted_string("foo*"), "foo*")
self.assertEqual(bind_value_for_formatted_string("foo\\*"), "foo\\*")


class CPETestCase(unittest.TestCase):
def test_uri_binding(self):
cpe_string = "cpe:/o:microsoft:windows_xp:::pro"
Expand Down Expand Up @@ -91,7 +193,6 @@ def test_uri_binding(self):
cpe = CPE.from_string(
"cpe:/a:foo%5cbar:big%24money_manager_2010:::~~special~ipod_touch~80gb~"
)
print(repr(cpe))
self.assertEqual(
str(cpe),
"cpe:/a:foo%5cbar:big%24money_manager_2010:::~~special~ipod_touch~80gb~",
Expand Down

0 comments on commit be2b808

Please sign in to comment.