From e81960fb82977df7ace77f3c03a3b2d9a0559075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?tonghuaroot=20=28=E7=AB=A5=E8=AF=9D=29?= Date: Tue, 25 Aug 2026 20:20:55 +0800 Subject: [PATCH 1/4] gh-152204: Validate date and time fields in `_pydatetime.date{time}.fromisoformat` (#152205) Co-authored-by: Stan Ulbrych --- Lib/_pydatetime.py | 30 ++++++++++++------- Lib/test/datetimetester.py | 19 +++++++++++- ...-06-25-14-05-00.gh-issue-152204.k9Qm3v.rst | 6 ++++ 3 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-25-14-05-00.gh-issue-152204.k9Qm3v.rst diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index c47f4e671b39def..6f53f45d524c991 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -355,19 +355,30 @@ def _find_isoformat_datetime_separator(dtstr): return 8 +def _read_isoformat_component(s, n): + # The caller has verified the string is ASCII, so isdigit() matches only + # the ASCII digits accepted by the C parser. + if len(s) != n or not s.isdigit(): + raise ValueError("Invalid isoformat string") + return int(s) + + def _parse_isoformat_date(dtstr): # It is assumed that this is an ASCII-only string of lengths 7, 8 or 10, # see the comment on Modules/_datetimemodule.c:_find_isoformat_datetime_separator if len(dtstr) not in (7, 8, 10): raise ValueError("Invalid isoformat string") - year = int(dtstr[0:4]) + if not dtstr.isascii(): + raise ValueError("Invalid isoformat string") + + year = _read_isoformat_component(dtstr[0:4], 4) has_sep = dtstr[4] == '-' pos = 4 + has_sep if dtstr[pos:pos + 1] == "W": # YYYY-?Www-?D? pos += 1 - weekno = int(dtstr[pos:pos + 2]) + weekno = _read_isoformat_component(dtstr[pos:pos + 2], 2) pos += 2 dayno = 1 @@ -377,17 +388,17 @@ def _parse_isoformat_date(dtstr): pos += has_sep - dayno = int(dtstr[pos:pos + 1]) + dayno = _read_isoformat_component(dtstr[pos:pos + 1], 1) return list(_isoweek_to_gregorian(year, weekno, dayno)) else: - month = int(dtstr[pos:pos + 2]) + month = _read_isoformat_component(dtstr[pos:pos + 2], 2) pos += 2 if (dtstr[pos:pos + 1] == "-") != has_sep: raise ValueError("Inconsistent use of dash separator") pos += has_sep - day = int(dtstr[pos:pos + 2]) + day = _read_isoformat_component(dtstr[pos:pos + 2], 2) return [year, month, day] @@ -402,10 +413,7 @@ def _parse_hh_mm_ss_ff(tstr): time_comps = [0, 0, 0, 0] pos = 0 for comp in range(0, 3): - if (len_str - pos) < 2: - raise ValueError("Incomplete time component") - - time_comps[comp] = int(tstr[pos:pos+2]) + time_comps[comp] = _read_isoformat_component(tstr[pos:pos+2], 2) pos += 2 next_char = tstr[pos:pos+1] @@ -426,7 +434,7 @@ def _parse_hh_mm_ss_ff(tstr): raise ValueError("Invalid microsecond separator") else: pos += 1 - if not all(map(_is_ascii_digit, tstr[pos:])): + if not tstr[pos:].isdigit(): raise ValueError("Non-digit values in fraction") len_remainder = len_str - pos @@ -447,6 +455,8 @@ def _parse_isoformat_time(tstr): len_str = len(tstr) if len_str < 2: raise ValueError("Isoformat time too short") + if not tstr.isascii(): + raise ValueError("Invalid isoformat string") # This is equivalent to re.search('[+-Z]', tstr), but faster tz_pos = (tstr.find('-') + 1 or tstr.find('+') + 1 or tstr.find('Z') + 1) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index c11e9c068bed3bd..dae71a6b6679a0f 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2106,7 +2106,15 @@ def test_fromisoformat_fails(self): '10000-W25-1', # Invalid year '2020-W25-0', # Invalid day-of-week '2020-W25-8', # Invalid day-of-week - '٢025-03-09' # Unicode characters + # gh-152204: each fixed-width field must be exactly N ASCII digits + '2020+12', # '+' in a basic-format field + '2020 12', # space in a basic-format field + '+020-06-15', # leading sign in the year + '202012+9', # '+' in the day field + '2020-W 5', # space in the week number + '2020061', # 7 chars: day slice reads a 1-character tail + '2020-W2', # 1-digit week number + '٢025-03-09', # Unicode characters '2009\ud80002\ud80028', # Separators are surrogate codepoints ] @@ -3758,6 +3766,15 @@ def test_fromisoformat_fails_datetime(self): '2009-04-19T12:30:45-00:90:00', # Time zone field out from range '2009-04-19T12:30:45-00:00:90', # Time zone field out from range '2020-2020', # Ambiguous 9-char date portion + # gh-152204: each time field must be exactly N ASCII digits + '2020-12-12T0٥:02:03', # Unicode digit in the hour + '2020-12-12T01:0٥:03', # Unicode digit in the minute + '2020-12-12T01:02:0٥', # Unicode digit in the second + '2020-12-12T01:02:03.٥', # Unicode digit in the fraction + '2020-12-12T01:02:03.4_6', # underscore in the fraction + '2020-12-12T01:02:03+0٥:00', # Unicode digit in the tz hour + '2020-12-12T01:02:03+01:0٥', # Unicode digit in the tz minute + '20201212T0102٣٤', # Unicode digits in the basic-format time '2009-04-19T12:30:45.+05:00', # Empty fraction before offset '2009-04-19T12:30:45.-05:00', # Empty fraction before offset '2009-04-19T12:30:45.Z', # Empty fraction before Z diff --git a/Misc/NEWS.d/next/Library/2026-06-25-14-05-00.gh-issue-152204.k9Qm3v.rst b/Misc/NEWS.d/next/Library/2026-06-25-14-05-00.gh-issue-152204.k9Qm3v.rst new file mode 100644 index 000000000000000..1ba1f872eb41a61 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-25-14-05-00.gh-issue-152204.k9Qm3v.rst @@ -0,0 +1,6 @@ +Fix the pure-Python implementations of :meth:`datetime.date.fromisoformat`, +:meth:`datetime.time.fromisoformat` and :meth:`datetime.datetime.fromisoformat` +silently accepting some malformed ISO 8601 strings, such as non-ASCII digits or +a sign in a fixed-width field (for example ``'2020+12'`` or ``'20201212T0102٣٤'``). +Each field is now required to be exactly *N* ASCII digits, matching the C +implementation. From 223ee6d5d744abb75de2289740dfff1b2cd9cc65 Mon Sep 17 00:00:00 2001 From: Micah Lindstrom <59303202+ukanuk@users.noreply.github.com> Date: Tue, 25 Aug 2026 06:03:27 -0700 Subject: [PATCH 2/4] Documentation on handling a corrupt nuget.config when installing packages from Nuget (GH-154039) --- Doc/using/windows.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index 40854332af0b24f..3a32c193e3a8fc8 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -1226,6 +1226,10 @@ for the 64-bit version, `www.nuget.org/packages/pythonx86 `www.nuget.org/packages/pythonarm64 `_ for the ARM64 version +Resolve error ``Unable to find package 'python'`` from a misconfigured ``nuget.config`` using:: + + nuget.exe sources add -Name "nuget.org" -source "https://api.nuget.org/v3/index.json" + Free-threaded packages ---------------------- From 2736620a593976d97876606d71d3e8fef7d1c0c7 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Tue, 25 Aug 2026 16:00:10 +0100 Subject: [PATCH 3/4] Notify Stan about Turtle related things (#156361) --- .github/CODEOWNERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5cff59e83dd1dd4..4d9e73db48ae57c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -598,6 +598,12 @@ Doc/library/tomllib.rst @encukou @hauntsaninja Lib/test/test_tomllib/ @encukou @hauntsaninja Lib/tomllib/ @encukou @hauntsaninja +# Turtle +Doc/library/turtle.rst @StanFromIreland +Doc/library/turtle-star.png @StanFromIreland +Lib/test/test_turtle.py @StanFromIreland +Lib/turtle.py @StanFromIreland + # Typing Doc/library/typing.rst @JelleZijlstra @AlexWaygood Lib/test/test_typing.py @JelleZijlstra @AlexWaygood From 5f31aeef60cd6397938e754e32530c25f59524ab Mon Sep 17 00:00:00 2001 From: Advit Arora Date: Tue, 25 Aug 2026 21:03:29 +0530 Subject: [PATCH 4/4] gh-155193: Fix the *canonical* decoder lists in the 3.15 What's New and NEWS (#156302) --- Doc/whatsnew/3.15.rst | 2 +- Misc/NEWS.d/3.15.0b1.rst | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 9a1efd811eb2bc7..bf9aceaa20b2b47 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1009,7 +1009,7 @@ base64 * Added the *canonical* parameter in :func:`~base64.b32decode`, :func:`~base64.b32hexdecode`, - :func:`~base64.b64decode`, :func:`~base64.urlsafe_b64decode`, + :func:`~base64.b64decode`, :func:`~base64.a85decode`, :func:`~base64.b85decode`, and :func:`~base64.z85decode`, to reject encodings with non-zero padding bits or other non-canonical diff --git a/Misc/NEWS.d/3.15.0b1.rst b/Misc/NEWS.d/3.15.0b1.rst index e0fcab0eef870fc..b53733f7b5df3bb 100644 --- a/Misc/NEWS.d/3.15.0b1.rst +++ b/Misc/NEWS.d/3.15.0b1.rst @@ -1081,10 +1081,10 @@ Deprecate :meth:`http.cookies.Morsel.js_output` and .. nonce: iHWO0v .. section: Library -Add a *canonical* keyword-only parameter to the base16, base32, base64, -base85, ascii85, and Z85 decoders in :mod:`base64` and :mod:`binascii`. When -true, encodings with non-zero padding bits (base16/32/64) or non-canonical -encodings (base85/ascii85) are rejected. Single-character final groups in +Add a *canonical* keyword-only parameter to the base32, base64, base85, +ascii85, and Z85 decoders in :mod:`base64` and :mod:`binascii`. When true, +encodings with non-zero padding bits (base32/64) or non-canonical encodings +(base85/ascii85) are rejected. Single-character final groups in :func:`binascii.a2b_ascii85` and :func:`binascii.a2b_base85` are now always rejected as encoding violations, regardless of *canonical*; previously they were silently ignored and produced no output bytes.