diff --git a/pontos/cpe/_cpe.py b/pontos/cpe/_cpe.py index b912c6bd0..befc0880d 100644 --- a/pontos/cpe/_cpe.py +++ b/pontos/cpe/_cpe.py @@ -328,6 +328,36 @@ def unbind_value_uri(value: Optional[str]) -> Optional[str]: return result +def unquote_attribute_value(value: Optional[str]) -> Optional[str]: + """ + Unquote a Well-Formed CPE Name Data Model (WFN) attribute value + """ + if not value or "\\" not in value: + # do nothing + return value + + index = 0 + result = "" + while index < len(value): + c = value[index] + if c == "\\": + next_c = value[index + 1] + if next_c in ["*", "?"]: + # keep escaped asterisks and question marks + result += f"{c}{next_c}" + else: + result += next_c + + index += 2 + continue + else: + result += c + + index += 1 + + return result + + def split_cpe(cpe: str) -> list[str]: """ Split a CPE into its parts diff --git a/tests/cpe/test_cpe.py b/tests/cpe/test_cpe.py index 3712ce651..a8f3c64d4 100644 --- a/tests/cpe/test_cpe.py +++ b/tests/cpe/test_cpe.py @@ -12,6 +12,7 @@ convert_double_backslash, split_cpe, unbind_value_from_formatted_string, + unquote_attribute_value, ) @@ -163,6 +164,26 @@ def test_unchanged(self): self.assertEqual(bind_value_for_formatted_string("foo\\*"), "foo\\*") +class UnquoteAttributeValueTestCase(unittest.TestCase): + def test_unchanged(self): + self.assertIsNone(unquote_attribute_value(None)) + self.assertEqual(unquote_attribute_value(""), "") + self.assertEqual(unquote_attribute_value(ANY), ANY) + self.assertEqual(unquote_attribute_value("?"), "?") + self.assertEqual(unquote_attribute_value("foo-bar"), "foo-bar") + self.assertEqual(unquote_attribute_value("foo_bar"), "foo_bar") + self.assertEqual(unquote_attribute_value("1.2.3"), "1.2.3") + + def test_special(self): + self.assertEqual(unquote_attribute_value("foo\\?bar"), "foo\\?bar") + self.assertEqual(unquote_attribute_value("foo\\*bar"), "foo\\*bar") + + def test_unquote(self): + self.assertEqual(unquote_attribute_value("foo\\\\bar"), "foo\\bar") + self.assertEqual(unquote_attribute_value("foo\\:bar"), "foo:bar") + self.assertEqual(unquote_attribute_value("1\\.2\\.3"), "1.2.3") + + class CPETestCase(unittest.TestCase): def test_uri_binding(self): cpe_string = "cpe:/o:microsoft:windows_xp:::pro"