diff --git a/cyclonedx/model/component.py b/cyclonedx/model/component.py index 89e7020d..984e1db9 100644 --- a/cyclonedx/model/component.py +++ b/cyclonedx/model/component.py @@ -63,6 +63,16 @@ from .license import License, LicenseRepository from .release_note import ReleaseNotes +CPE_REGEX = re.compile( + r'([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9._\-~%]*){0,6})|' + r'(cpe:2\.3:[aho*-](:(((\?*|\*?)([a-zA-Z0-9\-._]|' + r'(\\[\\\*\?!\"#\$%&\'\(\)\+,/:;<=>@\[\]\^`\{\|\}~]))+(\?*|\*?))|' + r'[\*\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|' + r'[\*\-]))(:(((\?*|\*?)([a-zA-Z0-9\-._]|' + r'(\\[\\\*\?!\"#\$%&\'\(\)\+,/:;<=>@\[\]\^`\{\|\}~]))+(\?*|' + r'\*?))|[\*\-])){4})' +) + @serializable.serializable_class class Commit: @@ -1457,6 +1467,8 @@ def cpe(self) -> Optional[str]: @cpe.setter def cpe(self, cpe: Optional[str]) -> None: + if cpe and not CPE_REGEX.fullmatch(cpe): + raise ValueError(f'Invalid CPE format: {cpe}') self._cpe = cpe @property diff --git a/tests/test_model_component.py b/tests/test_model_component.py index c25fdc91..3eb2d773 100644 --- a/tests/test_model_component.py +++ b/tests/test_model_component.py @@ -123,6 +123,7 @@ def test_empty_basic_component(self) -> None: self.assertSetEqual(c.external_references, set()) self.assertFalse(c.properties) self.assertIsNone(c.release_notes) + self.assertIsNone(c.cpe) self.assertEqual(len(c.components), 0) self.assertEqual(len(c.get_all_nested_components(include_self=True)), 1) @@ -283,6 +284,16 @@ def test_nested_components_2(self) -> None: self.assertEqual(3, len(comp_b.get_all_nested_components(include_self=True))) self.assertEqual(2, len(comp_b.get_all_nested_components(include_self=False))) + def test_cpe_validation_valid(self) -> None: + cpe = 'cpe:2.3:a:python:setuptools:50.3.2:*:*:*:*:*:*:*' + c = Component(name='test-component', cpe=cpe) + self.assertEqual(c.cpe, cpe) + + def test_cpe_validation_invalid_format(self) -> None: + invalid_cpe = 'invalid-cpe-string' + with self.assertRaises(ValueError): + Component(name='test-component', cpe=invalid_cpe) + class TestModelComponentEvidence(TestCase):