Skip to content

Commit

Permalink
Add CVE and CWE roles (#11781)
Browse files Browse the repository at this point in the history
Co-authored-by: Hugo van Kemenade <[email protected]>
Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Adam Turner <[email protected]>
  • Loading branch information
4 people authored Oct 5, 2024
1 parent f7fa020 commit 09ab6ed
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ Features added
such as :py:mod:`time` or :py:mod:`datetime` in :file:`conf.py`.
See :ref:`the docs <config-copyright>` for further detail.
Patch by Adam Turner.
* #11781: Add roles for referencing CVEs (:rst:role:`:cve: <cve>`)
and CWEs (:rst:role:`:cwe: <cwe>`).
Patch by Hugo van Kemenade.

Bugs fixed
----------
Expand Down
28 changes: 28 additions & 0 deletions doc/usage/restructuredtext/roles.rst
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,34 @@ There is also an :rst:role:`index` role to generate index entries.

The following roles generate external links:

.. rst:role:: cve
A reference to a `Common Vulnerabilities and Exposures`_ record.
This generates appropriate index entries.
The text "CVE *number*\ " is generated;
with a link to an online copy of the specified CVE.
You can link to a specific section by using ``:cve:`number#anchor```.

.. _Common Vulnerabilities and Exposures: https://www.cve.org/

For example: :cve:`2020-10735`

.. versionadded:: 8.1

.. rst:role:: cwe
A reference to a `Common Weakness Enumeration`_.
This generates appropriate index entries.
The text "CWE *number*\ " is generated; in the HTML output,
with a link to an online copy of the specified CWE.
You can link to a specific section by using ``:cwe:`number#anchor```.

.. _Common Weakness Enumeration: https://cwe.mitre.org/

For example: :cwe:`787`

.. versionadded:: 8.1

.. rst:role:: pep
A reference to a Python Enhancement Proposal. This generates appropriate
Expand Down
4 changes: 4 additions & 0 deletions sphinx/environment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
'image_loading': 'link',
'embed_stylesheet': False,
'cloak_email_addresses': True,
'cve_base_url': 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-',
'cve_references': None,
'cwe_base_url': 'https://cwe.mitre.org/data/definitions/',
'cwe_references': None,
'pep_base_url': 'https://peps.python.org/',
'pep_references': None,
'rfc_base_url': 'https://datatracker.ietf.org/doc/html/',
Expand Down
93 changes: 93 additions & 0 deletions sphinx/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,94 @@ def process_link(
return result


class CVE(ReferenceRole):
def run(self) -> tuple[list[Node], list[system_message]]:
target_id = f'index-{self.env.new_serialno("index")}'
entries = [
(
'single',
_('Common Vulnerabilities and Exposures; CVE %s') % self.target,
target_id,
'',
None,
)
]

index = addnodes.index(entries=entries)
target = nodes.target('', '', ids=[target_id])
self.inliner.document.note_explicit_target(target)

try:
refuri = self.build_uri()
reference = nodes.reference(
'', '', internal=False, refuri=refuri, classes=['cve']
)
if self.has_explicit_title:
reference += nodes.strong(self.title, self.title)
else:
title = f'CVE {self.title}'
reference += nodes.strong(title, title)
except ValueError:
msg = self.inliner.reporter.error(
__('invalid CVE number %s') % self.target, line=self.lineno
)
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
return [prb], [msg]

return [index, target, reference], []

def build_uri(self) -> str:
base_url = self.inliner.document.settings.cve_base_url
ret = self.target.split('#', 1)
if len(ret) == 2:
return f'{base_url}{ret[0]}#{ret[1]}'
return f'{base_url}{ret[0]}'


class CWE(ReferenceRole):
def run(self) -> tuple[list[Node], list[system_message]]:
target_id = f'index-{self.env.new_serialno("index")}'
entries = [
(
'single',
_('Common Weakness Enumeration; CWE %s') % self.target,
target_id,
'',
None,
)
]

index = addnodes.index(entries=entries)
target = nodes.target('', '', ids=[target_id])
self.inliner.document.note_explicit_target(target)

try:
refuri = self.build_uri()
reference = nodes.reference(
'', '', internal=False, refuri=refuri, classes=['cwe']
)
if self.has_explicit_title:
reference += nodes.strong(self.title, self.title)
else:
title = f'CWE {self.title}'
reference += nodes.strong(title, title)
except ValueError:
msg = self.inliner.reporter.error(
__('invalid CWE number %s') % self.target, line=self.lineno
)
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
return [prb], [msg]

return [index, target, reference], []

def build_uri(self) -> str:
base_url = self.inliner.document.settings.cwe_base_url
ret = self.target.split('#', 1)
if len(ret) == 2:
return f'{base_url}{int(ret[0])}.html#{ret[1]}'
return f'{base_url}{int(ret[0])}.html'


class PEP(ReferenceRole):
def run(self) -> tuple[list[Node], list[system_message]]:
target_id = 'index-%s' % self.env.new_serialno('index')
Expand Down Expand Up @@ -454,12 +542,17 @@ def code_role(
'download': XRefRole(nodeclass=addnodes.download_reference),
# links to anything
'any': AnyXRefRole(warn_dangling=True),
# external links
'cve': CVE(),
'cwe': CWE(),
'pep': PEP(),
'rfc': RFC(),
# emphasised things
'guilabel': GUILabel(),
'menuselection': MenuSelection(),
'file': EmphasizedLiteral(),
'samp': EmphasizedLiteral(),
# other
'abbr': Abbreviation(),
'manpage': Manpage(),
}
Expand Down
68 changes: 68 additions & 0 deletions tests/test_markup/test_markup.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,74 @@ def get(name):
@pytest.mark.parametrize(
('type', 'rst', 'html_expected', 'latex_expected'),
[
(
# cve role
'verify',
':cve:`2020-10735`',
(
'<p><span class="target" id="index-0"></span><a class="cve reference external" '
'href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735">'
'<strong>CVE 2020-10735</strong></a></p>'
),
(
'\\sphinxAtStartPar\n'
'\\index{Common Vulnerabilities and Exposures@\\spxentry{Common Vulnerabilities and Exposures}'
'!CVE 2020\\sphinxhyphen{}10735@\\spxentry{CVE 2020\\sphinxhyphen{}10735}}'
'\\sphinxhref{https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735}'
'{\\sphinxstylestrong{CVE 2020\\sphinxhyphen{}10735}}'
),
),
(
# cve role with anchor
'verify',
':cve:`2020-10735#id1`',
(
'<p><span class="target" id="index-0"></span><a class="cve reference external" '
'href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735#id1">'
'<strong>CVE 2020-10735#id1</strong></a></p>'
),
(
'\\sphinxAtStartPar\n'
'\\index{Common Vulnerabilities and Exposures@\\spxentry{Common Vulnerabilities and Exposures}'
'!CVE 2020\\sphinxhyphen{}10735\\#id1@\\spxentry{CVE 2020\\sphinxhyphen{}10735\\#id1}}'
'\\sphinxhref{https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735\\#id1}'
'{\\sphinxstylestrong{CVE 2020\\sphinxhyphen{}10735\\#id1}}'
),
),
(
# cwe role
'verify',
':cwe:`787`',
(
'<p><span class="target" id="index-0"></span><a class="cwe reference external" '
'href="https://cwe.mitre.org/data/definitions/787.html">'
'<strong>CWE 787</strong></a></p>'
),
(
'\\sphinxAtStartPar\n'
'\\index{Common Weakness Enumeration@\\spxentry{Common Weakness Enumeration}'
'!CWE 787@\\spxentry{CWE 787}}'
'\\sphinxhref{https://cwe.mitre.org/data/definitions/787.html}'
'{\\sphinxstylestrong{CWE 787}}'
),
),
(
# cwe role with anchor
'verify',
':cwe:`787#id1`',
(
'<p><span class="target" id="index-0"></span><a class="cwe reference external" '
'href="https://cwe.mitre.org/data/definitions/787.html#id1">'
'<strong>CWE 787#id1</strong></a></p>'
),
(
'\\sphinxAtStartPar\n'
'\\index{Common Weakness Enumeration@\\spxentry{Common Weakness Enumeration}'
'!CWE 787\\#id1@\\spxentry{CWE 787\\#id1}}'
'\\sphinxhref{https://cwe.mitre.org/data/definitions/787.html\\#id1}'
'{\\sphinxstylestrong{CWE 787\\#id1}}'
),
),
(
# pep role
'verify',
Expand Down

0 comments on commit 09ab6ed

Please sign in to comment.