From 0a3e69038ebfc01fd98ddf9f2ac871519209a8a5 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 5 Apr 2023 22:01:00 +0100 Subject: [PATCH 1/2] Deprecate ``md5`` and ``sha1`` wrappers in ``sphinx.util`` --- doc/extdev/deprecated.rst | 10 ++++++++++ sphinx/builders/html/__init__.py | 5 +++-- sphinx/ext/graphviz.py | 7 ++++--- sphinx/ext/imgmath.py | 5 +++-- sphinx/ext/inheritance_diagram.py | 4 ++-- sphinx/transforms/post_transforms/images.py | 9 +++++---- sphinx/util/__init__.py | 18 +++++++++--------- tests/test_build_html.py | 6 +++--- 8 files changed, 39 insertions(+), 25 deletions(-) diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 863af316543..1c73a478178 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -22,6 +22,16 @@ The following is a list of deprecated interfaces. - Removed - Alternatives + * - ``sphinx.util.md5`` + - 7.2 + - 9.0 + - ``hashlib.md5`` + + * - ``sphinx.util.sha1`` + - 7.2 + - 9.0 + - ``hashlib.sha1`` + * - ``sphinx.util.osutil.cd`` - 6.2 - 8.0 diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 9d76636badc..5a4cde4115e 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +import hashlib import html import os import posixpath @@ -37,7 +38,7 @@ from sphinx.locale import _, __ from sphinx.search import js_index from sphinx.theming import HTMLThemeFactory -from sphinx.util import isurl, logging, md5 +from sphinx.util import isurl, logging from sphinx.util.display import progress_message, status_iterator from sphinx.util.docutils import new_document from sphinx.util.fileutil import copy_asset @@ -80,7 +81,7 @@ def get_stable_hash(obj: Any) -> str: return get_stable_hash(list(obj.items())) elif isinstance(obj, (list, tuple)): obj = sorted(get_stable_hash(o) for o in obj) - return md5(str(obj).encode()).hexdigest() + return hashlib.md5(str(obj).encode(), usedforsecurity=False).hexdigest() def convert_locale_to_language_tag(locale: str | None) -> str | None: diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index 941c08372c1..3701a6e5922 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -6,6 +6,7 @@ import posixpath import re import subprocess +from hashlib import sha1 from os import path from subprocess import CalledProcessError from typing import TYPE_CHECKING, Any @@ -18,7 +19,7 @@ from sphinx.application import Sphinx from sphinx.errors import SphinxError from sphinx.locale import _, __ -from sphinx.util import logging, sha1 +from sphinx.util import logging from sphinx.util.docutils import SphinxDirective, SphinxTranslator from sphinx.util.i18n import search_image_for_language from sphinx.util.nodes import set_source_info @@ -62,7 +63,7 @@ def parse(self, dot: str) -> None: if self.id == '%3': # graphviz generates wrong ID if graph name not specified # https://gitlab.com/graphviz/graphviz/issues/1327 - hashed = sha1(dot.encode()).hexdigest() + hashed = sha1(dot.encode(), usedforsecurity=False).hexdigest() self.id = 'grapviz%s' % hashed[-10:] self.content[0] = self.content[0].replace('%3', self.id) @@ -221,7 +222,7 @@ def render_dot(self: SphinxTranslator, code: str, options: dict, format: str, hashkey = (code + str(options) + str(graphviz_dot) + str(self.builder.config.graphviz_dot_args)).encode() - fname = f'{prefix}-{sha1(hashkey).hexdigest()}.{format}' + fname = f'{prefix}-{sha1(hashkey, usedforsecurity=False).hexdigest()}.{format}' relfn = posixpath.join(self.builder.imgpath, fname) outfn = path.join(self.builder.outdir, self.builder.imagedir, fname) diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index 34d716686d3..b69af8615d9 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -7,6 +7,7 @@ import shutil import subprocess import tempfile +from hashlib import sha1 from os import path from subprocess import CalledProcessError from typing import Any @@ -21,7 +22,7 @@ from sphinx.config import Config from sphinx.errors import SphinxError from sphinx.locale import _, __ -from sphinx.util import logging, sha1 +from sphinx.util import logging from sphinx.util.math import get_node_equation_number, wrap_displaymath from sphinx.util.osutil import ensuredir from sphinx.util.png import read_png_depth, write_png_depth @@ -239,7 +240,7 @@ def render_math( self.builder.config, self.builder.confdir) - filename = f"{sha1(latex.encode()).hexdigest()}.{image_format}" + filename = f"{sha1(latex.encode(), usedforsecurity=False).hexdigest()}.{image_format}" generated_path = path.join(self.builder.outdir, self.builder.imagedir, 'math', filename) ensuredir(path.dirname(generated_path)) if path.isfile(generated_path): diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index a25bbcbc32a..0afcb3431fd 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -31,6 +31,7 @@ class E(B): pass from __future__ import annotations import builtins +import hashlib import inspect import re from collections.abc import Iterable @@ -52,7 +53,6 @@ class E(B): pass render_dot_latex, render_dot_texinfo, ) -from sphinx.util import md5 from sphinx.util.docutils import SphinxDirective from sphinx.util.typing import OptionSpec from sphinx.writers.html import HTML5Translator @@ -392,7 +392,7 @@ def run(self) -> list[Node]: def get_graph_hash(node: inheritance_diagram) -> str: encoded = (node['content'] + str(node['parts'])).encode() - return md5(encoded).hexdigest()[-10:] + return hashlib.md5(encoded, usedforsecurity=False).hexdigest()[-10:] def html_visit_inheritance_diagram(self: HTML5Translator, node: inheritance_diagram) -> None: diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index 1eb713e85f6..192f7686ddc 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -4,6 +4,7 @@ import os import re +from hashlib import sha1 from math import ceil from typing import Any @@ -12,7 +13,7 @@ from sphinx.application import Sphinx from sphinx.locale import __ from sphinx.transforms import SphinxTransform -from sphinx.util import logging, requests, sha1 +from sphinx.util import logging, requests from sphinx.util.http_date import epoch_to_rfc1123, rfc1123_to_epoch from sphinx.util.images import get_image_extension, guess_mimetype, parse_data_uri from sphinx.util.osutil import ensuredir @@ -57,13 +58,13 @@ def handle(self, node: nodes.image) -> None: basename = basename.split('?')[0] if basename == '' or len(basename) > MAX_FILENAME_LEN: filename, ext = os.path.splitext(node['uri']) - basename = sha1(filename.encode()).hexdigest() + ext + basename = sha1(filename.encode(), usedforsecurity=False).hexdigest() + ext basename = re.sub(CRITICAL_PATH_CHAR_RE, "_", basename) dirname = node['uri'].replace('://', '/').translate({ord("?"): "/", ord("&"): "/"}) if len(dirname) > MAX_FILENAME_LEN: - dirname = sha1(dirname.encode()).hexdigest() + dirname = sha1(dirname.encode(), usedforsecurity=False).hexdigest() ensuredir(os.path.join(self.imagedir, dirname)) path = os.path.join(self.imagedir, dirname, basename) @@ -125,7 +126,7 @@ def handle(self, node: nodes.image) -> None: return ensuredir(os.path.join(self.imagedir, 'embeded')) - digest = sha1(image.data).hexdigest() + digest = sha1(image.data, usedforsecurity=False).hexdigest() path = os.path.join(self.imagedir, 'embeded', digest + ext) self.app.env.original_image_uri[path] = node['uri'] diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index dc490f53785..c96aa2de1bf 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -104,21 +104,19 @@ def __setstate__(self, state: set[str]) -> None: self._existing = state -def md5(data=b'', **kwargs): - """Wrapper around hashlib.md5 +def _md5(data=b'', **_kw): + """Deprecated wrapper around hashlib.md5 - Call with 'usedforsecurity=False'. + To be removed in Sphinx 9.0 """ - return hashlib.md5(data, usedforsecurity=False) -def sha1(data=b'', **kwargs): - """Wrapper around hashlib.sha1 +def _sha1(data=b'', **_kw): + """Deprecated wrapper around hashlib.sha1 - Call with 'usedforsecurity=False'. + To be removed in Sphinx 9.0 """ - return hashlib.sha1(data, usedforsecurity=False) @@ -131,7 +129,7 @@ class DownloadFiles(dict): def add_file(self, docname: str, filename: str) -> str: if filename not in self: - digest = md5(filename.encode()).hexdigest() + digest = hashlib.md5(filename.encode(), usedforsecurity=False).hexdigest() dest = f'{digest}/{os.path.basename(filename)}' self[filename] = (set(), dest) @@ -349,6 +347,8 @@ def _xml_name_checker(): 'format_exception_cut_frames': (_exceptions.format_exception_cut_frames, 'sphinx.exceptions.format_exception_cut_frames'), 'xmlname_checker': (_xml_name_checker, 'sphinx.builders.epub3._XML_NAME_PATTERN'), + 'md5': (_md5, ''), + 'sha1': (_sha1, ''), } diff --git a/tests/test_build_html.py b/tests/test_build_html.py index c6a36209892..2604f3df920 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -1,5 +1,6 @@ """Test the HTML builder and check output against XPath.""" +import hashlib import os import re from itertools import chain, cycle @@ -12,7 +13,6 @@ from sphinx.builders.html import validate_html_extra_path, validate_html_static_path from sphinx.errors import ConfigError from sphinx.testing.util import strip_escseq -from sphinx.util import md5 from sphinx.util.inventory import InventoryFile FIGURE_CAPTION = ".//figure/figcaption/p" @@ -474,9 +474,9 @@ def test_html_download(app): @pytest.mark.sphinx('html', testroot='roles-download') def test_html_download_role(app, status, warning): app.build() - digest = md5(b'dummy.dat').hexdigest() + digest = hashlib.md5(b'dummy.dat', usedforsecurity=False).hexdigest() assert (app.outdir / '_downloads' / digest / 'dummy.dat').exists() - digest_another = md5(b'another/dummy.dat').hexdigest() + digest_another = hashlib.md5(b'another/dummy.dat', usedforsecurity=False).hexdigest() assert (app.outdir / '_downloads' / digest_another / 'dummy.dat').exists() content = (app.outdir / 'index.html').read_text(encoding='utf8') From 57cbe55ee094fc9c8f29a153a587dccd89352900 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 25 Jul 2023 02:29:13 +0100 Subject: [PATCH 2/2] Add note --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 08a5ce35c0e..3a2f997ff47 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,9 @@ Incompatible changes Deprecated ---------- +* #11512: Deprecate ``sphinx.util.md5`` and ``sphinx.util.sha1``. + Use ``hashlib`` instead. + Features added --------------