Skip to content

Commit 15d9c19

Browse files
committed
feat: add cpe format validation
- Implemented regex-based validation for CPE format in the model. - Added tests to verify handling of invalid CPE strings. Signed-off-by: Saquib Saifee <[email protected]>
1 parent be4fd4b commit 15d9c19

File tree

2 files changed

+23
-0
lines changed

2 files changed

+23
-0
lines changed

cyclonedx/model/component.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@
6363
from .license import License, LicenseRepository
6464
from .release_note import ReleaseNotes
6565

66+
CPE_REGEX = re.compile(
67+
r'([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9._\-~%]*){0,6})|'
68+
r'(cpe:2\.3:[aho*-](:(((\?*|\*?)([a-zA-Z0-9\-._]|'
69+
r'(\\[\\\*\?!\"#\$%&\'\(\)\+,/:;<=>@\[\]\^`\{\|\}~]))+(\?*|\*?))|'
70+
r'[\*\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|'
71+
r'[\*\-]))(:(((\?*|\*?)([a-zA-Z0-9\-._]|'
72+
r'(\\[\\\*\?!\"#\$%&\'\(\)\+,/:;<=>@\[\]\^`\{\|\}~]))+(\?*|'
73+
r'\*?))|[\*\-])){4})'
74+
)
75+
6676

6777
@serializable.serializable_class
6878
class Commit:
@@ -1457,6 +1467,8 @@ def cpe(self) -> Optional[str]:
14571467

14581468
@cpe.setter
14591469
def cpe(self, cpe: Optional[str]) -> None:
1470+
if cpe and not CPE_REGEX.fullmatch(cpe):
1471+
raise ValueError(f'Invalid CPE format: {cpe}')
14601472
self._cpe = cpe
14611473

14621474
@property

tests/test_model_component.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def test_empty_basic_component(self) -> None:
123123
self.assertSetEqual(c.external_references, set())
124124
self.assertFalse(c.properties)
125125
self.assertIsNone(c.release_notes)
126+
self.assertIsNone(c.cpe)
126127
self.assertEqual(len(c.components), 0)
127128
self.assertEqual(len(c.get_all_nested_components(include_self=True)), 1)
128129

@@ -283,6 +284,16 @@ def test_nested_components_2(self) -> None:
283284
self.assertEqual(3, len(comp_b.get_all_nested_components(include_self=True)))
284285
self.assertEqual(2, len(comp_b.get_all_nested_components(include_self=False)))
285286

287+
def test_cpe_validation_valid(self) -> None:
288+
cpe = 'cpe:2.3:a:microsoft:internet_explorer:11:*:*:*:*:*:*:*'
289+
c = Component(name='test-component', cpe=cpe)
290+
self.assertEqual(c.cpe, cpe)
291+
292+
def test_cpe_validation_invalid_format(self) -> None:
293+
invalid_cpe = 'invalid-cpe-string'
294+
with self.assertRaises(ValueError):
295+
Component(name='test-component', cpe=invalid_cpe)
296+
286297

287298
class TestModelComponentEvidence(TestCase):
288299

0 commit comments

Comments
 (0)