From b7a1e6e73834dc8ca6e40f55b3787a0ba9d150b1 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 23 Jul 2024 17:45:54 +0200 Subject: [PATCH 1/4] Fix hashlib.file_digest and non-blocking I/O --- Lib/hashlib.py | 2 ++ Lib/test/test_hashlib.py | 9 +++++++++ .../2024-07-23-17-08-41.gh-issue-122179.0jZm9h.rst | 3 +++ 3 files changed, 14 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-07-23-17-08-41.gh-issue-122179.0jZm9h.rst diff --git a/Lib/hashlib.py b/Lib/hashlib.py index da0577023cf47d..110717aadf6473 100644 --- a/Lib/hashlib.py +++ b/Lib/hashlib.py @@ -231,6 +231,8 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18): view = memoryview(buf) while True: size = fileobj.readinto(buf) + if size is None: + raise BlockingIOError("I/O operation would block.") if size == 0: break # EOF digestobj.update(view[:size]) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 73d758a3631b3a..577fcaaebffa13 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -1185,6 +1185,15 @@ def test_file_digest(self): with open(os_helper.TESTFN, "wb") as f: hashlib.file_digest(f, "sha256") + class NonBlocking: + def readinto(self, buf): + return None + def readable(self): + return True + + with self.assertRaises(BlockingIOError): + hashlib.file_digest(NonBlocking(), hashlib.sha256) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2024-07-23-17-08-41.gh-issue-122179.0jZm9h.rst b/Misc/NEWS.d/next/Library/2024-07-23-17-08-41.gh-issue-122179.0jZm9h.rst new file mode 100644 index 00000000000000..2b0678f31e8ef6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-23-17-08-41.gh-issue-122179.0jZm9h.rst @@ -0,0 +1,3 @@ +:func:`hashlib.file_digest` now raises :exc:`BlockingIOError` when no data +is available during non-blocking I/O. Before, it added spurious null bytes +to the digest. From d571850fd03417c97efaa04375a575876c7ec90a Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 23 Jul 2024 17:47:54 +0200 Subject: [PATCH 2/4] Remove trailing space --- Lib/test/test_hashlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 577fcaaebffa13..77251939df98d9 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -1191,7 +1191,7 @@ def readinto(self, buf): def readable(self): return True - with self.assertRaises(BlockingIOError): + with self.assertRaises(BlockingIOError): hashlib.file_digest(NonBlocking(), hashlib.sha256) From 5669cce42111b0ec0a44f213794cf7123e392472 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 1 Aug 2024 17:43:17 +0200 Subject: [PATCH 3/4] Add documentation around this behavior --- Doc/library/hashlib.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/library/hashlib.rst b/Doc/library/hashlib.rst index 5d24b77e13bfce..bd63a147f56927 100644 --- a/Doc/library/hashlib.rst +++ b/Doc/library/hashlib.rst @@ -272,7 +272,10 @@ a file or file-like object. *fileobj* must be a file-like object opened for reading in binary mode. It accepts file objects from builtin :func:`open`, :class:`~io.BytesIO` instances, SocketIO objects from :meth:`socket.socket.makefile`, and - similar. The function may bypass Python's I/O and use the file descriptor + similar. *fileobj* must be opened in blocking mode, otherwise a + :exc:`BlockingIOError` may be raised. + + The function may bypass Python's I/O and use the file descriptor from :meth:`~io.IOBase.fileno` directly. *fileobj* must be assumed to be in an unknown state after this function returns or raises. It is up to the caller to close *fileobj*. From 612f7d43f0c9d3332953f44c1392b0a682234ffd Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 14 Apr 2025 12:27:17 +0200 Subject: [PATCH 4/4] Add versionchanged --- Doc/library/hashlib.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/hashlib.rst b/Doc/library/hashlib.rst index 0026b4a69d0dd8..56a37dce9d8888 100644 --- a/Doc/library/hashlib.rst +++ b/Doc/library/hashlib.rst @@ -304,6 +304,10 @@ a file or file-like object. .. versionadded:: 3.11 + .. versionchanged:: next + Now raises a :exc:`BlockingIOError` if the file is opened in blocking + mode. Previously, spurious null bytes were added to the digest. + Key derivation --------------