]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-122179: Fix hashlib.file_digest and non-blocking I/O (GH-132787)
authorGregory P. Smith <greg@krypto.org>
Mon, 21 Apr 2025 21:55:57 +0000 (14:55 -0700)
committerGitHub <noreply@github.com>
Mon, 21 Apr 2025 21:55:57 +0000 (21:55 +0000)
gh-122179: Fix hashlib.file_digest and non-blocking I/O (GH-122183)

* Fix hashlib.file_digest and non-blocking I/O
* Add documentation around this behavior
* Add versionchanged

(cherry picked from commit 2b47f46d7dc30d27b2486991fea4acd83553294b)

Co-authored-by: Sebastian Rittau <srittau@rittau.biz>
Doc/library/hashlib.rst
Lib/hashlib.py
Lib/test/test_hashlib.py
Misc/NEWS.d/next/Library/2024-07-23-17-08-41.gh-issue-122179.0jZm9h.rst [new file with mode: 0644]

index 7bf6152311f058b3573c3504d6bfd87ac1529d7d..ff15a08a792ed27df2039ff425e57b30b2a6a782 100644 (file)
@@ -270,7 +270,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*.
@@ -299,6 +302,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
 --------------
index 1b16441cb60ba7787b62ce6784656f6b9761409f..296210e5d30d1d746a905c59cf368374b8d78e4b 100644 (file)
@@ -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])
index a3693f5b8934f7b5ed4fc8a89ab287bfeba45cb2..48621f47af038b072469820a0643c7ce0cf4b498 100644 (file)
@@ -1190,6 +1190,15 @@ class KDFTests(unittest.TestCase):
             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 (file)
index 0000000..2b0678f
--- /dev/null
@@ -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.