diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py
index 800a2c18e3fa41..9a6f01dfb5e69a 100644
--- a/Lib/http/cookies.py
+++ b/Lib/http/cookies.py
@@ -391,21 +391,20 @@ def output(self, attrs=None, header="Set-Cookie:"):
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.OutputString())
-
def _js_output(self, attrs=None):
"""Internal implementation without deprecation warning."""
- import base64
+ import urllib.parse
# Print javascript
output_string = self.OutputString(attrs)
if _has_control_character(output_string):
raise CookieError("Control characters are not allowed in cookies")
# Base64-encode value to avoid template
# injection in cookie values.
- output_encoded = base64.b64encode(output_string.encode('utf-8')).decode("ascii")
+ output_encoded = urllib.parse.quote(output_string, safe='', encoding='utf-8')
return """
""" % (output_encoded,)
diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py
index cde268e3241850..d1df2ec42f0d14 100644
--- a/Lib/test/test_http_cookies.py
+++ b/Lib/test/test_http_cookies.py
@@ -1,11 +1,11 @@
# Simple test suite for http/cookies.py
-import base64
import copy
import unittest
import doctest
from http import cookies
import pickle
from test import support
+import urllib.parse
class CookieTests(unittest.TestCase):
@@ -152,21 +152,21 @@ def test_load(self):
self.assertEqual(C.output(['path']),
'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
- cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme; Version=1').decode('ascii')
+ cookie_encoded = urllib.parse.quote('Customer="WILE_E_COYOTE"; Path=/acme; Version=1', safe='', encoding='utf-8')
with self.assertWarnsRegex(DeprecationWarning, r"BaseCookie\.js_output"):
self.assertEqual(C.js_output(), fr"""
""")
- cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme').decode('ascii')
+ cookie_encoded = urllib.parse.quote('Customer="WILE_E_COYOTE"; Path=/acme', safe='', encoding='utf-8')
with self.assertWarnsRegex(DeprecationWarning, r"BaseCookie\.js_output"):
self.assertEqual(C.js_output(['path']), fr"""
""")
@@ -271,21 +271,21 @@ def test_quoted_meta(self):
self.assertEqual(C.output(['path']),
'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
- expected_encoded_cookie = base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1').decode('ascii')
+ expected_encoded_cookie = urllib.parse.quote('Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1', safe='', encoding='utf-8')
with self.assertWarnsRegex(DeprecationWarning, r"BaseCookie\.js_output"):
self.assertEqual(C.js_output(), fr"""
""")
- expected_encoded_cookie = base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme').decode('ascii')
+ expected_encoded_cookie = urllib.parse.quote('Customer=\"WILE_E_COYOTE\"; Path=/acme', safe='', encoding='utf-8')
with self.assertWarnsRegex(DeprecationWarning, r"BaseCookie\.js_output"):
self.assertEqual(C.js_output(['path']), fr"""
""")
@@ -376,13 +376,14 @@ def test_setter(self):
self.assertEqual(
M.output(),
"Set-Cookie: %s=%s; Path=/foo" % (i, "%s_coded_val" % i))
- expected_encoded_cookie = base64.b64encode(
- ("%s=%s; Path=/foo" % (i, "%s_coded_val" % i)).encode("ascii")
- ).decode('ascii')
+ expected_encoded_cookie = urllib.parse.quote(
+ "%s=%s; Path=/foo" % (i, "%s_coded_val" % i),
+ safe='', encoding='utf-8',
+ )
expected_js_output = """
""" % (expected_encoded_cookie,)
diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py
index 47450d3fd5976f..81a5477b496b5c 100644
--- a/Lib/test/test_tcl.py
+++ b/Lib/test/test_tcl.py
@@ -54,7 +54,11 @@ def test_eval_null_in_result(self):
def test_eval_surrogates_in_result(self):
tcl = self.interp
- self.assertEqual(tcl.eval(r'set a "<\ud83d\udcbb>"'), '<\U0001f4bb>')
+ result = tcl.eval(r'set a "<\ud83d\udcbb>"')
+ if sys.platform == 'win32':
+ self.assertEqual('<\ud83d\udcbb>', result)
+ else:
+ self.assertEqual('<\U0001f4bb>', result)
def testEvalException(self):
tcl = self.interp
@@ -289,7 +293,11 @@ def test_evalfile_surrogates_in_result(self):
set b "<\\ud83d\\udcbb>"
""")
tcl.evalfile(filename)
- self.assertEqual(tcl.eval('set b'), '<\U0001f4bb>')
+ result = tcl.eval('set b')
+ if sys.platform == 'win32':
+ self.assertEqual('<\ud83d\udcbb>', result)
+ else:
+ self.assertEqual('<\U0001f4bb>', result)
def testEvalFileException(self):
tcl = self.interp
diff --git a/Misc/NEWS.d/next/Windows/2026-05-06-21-36-53.gh-issue-124111.m4OBX8.rst b/Misc/NEWS.d/next/Windows/2026-05-06-21-36-53.gh-issue-124111.m4OBX8.rst
new file mode 100644
index 00000000000000..9a57536f1dc96b
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2026-05-06-21-36-53.gh-issue-124111.m4OBX8.rst
@@ -0,0 +1 @@
+Updated Windows builds to use Tcl/Tk 9.0.3.
diff --git a/Misc/externals.spdx.json b/Misc/externals.spdx.json
index 593fa01bf25ed1..9a571fba732ab4 100644
--- a/Misc/externals.spdx.json
+++ b/Misc/externals.spdx.json
@@ -108,46 +108,46 @@
"versionInfo": "3.50.4.0"
},
{
- "SPDXID": "SPDXRef-PACKAGE-tcl-core",
+ "SPDXID": "SPDXRef-PACKAGE-tcl",
"checksums": [
{
"algorithm": "SHA256",
- "checksumValue": "4c23f0dd3efcbe6f3a22c503a68d147617bb30c4f5290f1eb3eaacf0b460440b"
+ "checksumValue": "7a1d1f3a2b8f4484a9c2a027a157963c18f85a81785e85fcb5d1e3df6b6a4fd4"
}
],
- "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tcl-core-8.6.15.0.tar.gz",
+ "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tcl-9.0.3.0.tar.gz",
"externalRefs": [
{
"referenceCategory": "SECURITY",
- "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:8.6.15.0:*:*:*:*:*:*:*",
+ "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:9.0.3.0:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
}
],
"licenseConcluded": "NOASSERTION",
- "name": "tcl-core",
+ "name": "tcl",
"primaryPackagePurpose": "SOURCE",
- "versionInfo": "8.6.15.0"
+ "versionInfo": "9.0.3.0"
},
{
"SPDXID": "SPDXRef-PACKAGE-tk",
"checksums": [
{
"algorithm": "SHA256",
- "checksumValue": "0ae56d39bca92865f338529557a1e56d110594184b6dc5a91339c5675751e264"
+ "checksumValue": "54fb59df12c489c6264f5b7d3d7444b150d1e3d6561fd59cdb11483440cec000"
}
],
- "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tk-8.6.15.0.tar.gz",
+ "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tk-9.0.3.1.tar.gz",
"externalRefs": [
{
"referenceCategory": "SECURITY",
- "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:8.6.15.0:*:*:*:*:*:*:*",
+ "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:9.0.3.1:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
}
],
"licenseConcluded": "NOASSERTION",
"name": "tk",
"primaryPackagePurpose": "SOURCE",
- "versionInfo": "8.6.15.0"
+ "versionInfo": "9.0.3.1"
},
{
"SPDXID": "SPDXRef-PACKAGE-xz",
diff --git a/PCbuild/get_externals.bat b/PCbuild/get_externals.bat
index 405285b65dd270..368bc489bfa968 100644
--- a/PCbuild/get_externals.bat
+++ b/PCbuild/get_externals.bat
@@ -57,8 +57,8 @@ if NOT "%IncludeLibffiSrc%"=="false" set libraries=%libraries% libffi-3.4.4
if NOT "%IncludeSSLSrc%"=="false" set libraries=%libraries% openssl-3.5.6
set libraries=%libraries% mpdecimal-4.0.0
set libraries=%libraries% sqlite-3.50.4.0
-if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-core-8.6.15.0
-if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-8.6.15.0
+if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tcl-9.0.3.0
+if NOT "%IncludeTkinterSrc%"=="false" set libraries=%libraries% tk-9.0.3.1
set libraries=%libraries% xz-5.8.1.1
set libraries=%libraries% zlib-ng-2.2.4
set libraries=%libraries% zstd-1.5.7
@@ -80,7 +80,7 @@ echo.Fetching external binaries...
set binaries=
if NOT "%IncludeLibffi%"=="false" set binaries=%binaries% libffi-3.4.4
if NOT "%IncludeSSL%"=="false" set binaries=%binaries% openssl-bin-3.5.6
-if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-8.6.15.0
+if NOT "%IncludeTkinter%"=="false" set binaries=%binaries% tcltk-9.0.3.0
if NOT "%IncludeSSLSrc%"=="false" set binaries=%binaries% nasm-2.11.06
if NOT "%IncludeLLVM%"=="false" set binaries=%binaries% llvm-21.1.4.0
diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt
index 14aac0b0dc84b6..6aecbfff182dcb 100644
--- a/PCbuild/readme.txt
+++ b/PCbuild/readme.txt
@@ -247,7 +247,7 @@ _sqlite3
https://www.sqlite.org/
_tkinter
- Wraps version 8.6.15 of the Tk windowing system, which is downloaded
+ Wraps version 9.0.3 of the Tk windowing system, which is downloaded
from our binaries repository at
https://github.com/python/cpython-bin-deps.
diff --git a/PCbuild/tcltk.props b/PCbuild/tcltk.props
index a1da1155b881fd..28e8c0db4d1eaf 100644
--- a/PCbuild/tcltk.props
+++ b/PCbuild/tcltk.props
@@ -2,7 +2,7 @@
- 8.6.15.0
+ 9.0.3.0
$(TclVersion)
$([System.Version]::Parse($(TclVersion)).Major)
$([System.Version]::Parse($(TclVersion)).Minor)
@@ -12,7 +12,9 @@
$([System.Version]::Parse($(TkVersion)).Minor)
$([System.Version]::Parse($(TkVersion)).Build)
$([System.Version]::Parse($(TkVersion)).Revision)
- $(ExternalsDir)tcl-core-$(TclVersion)\
+
+ $(ExternalsDir)tcl-core-$(TclVersion)\
+ $(ExternalsDir)tcl-$(TclVersion)\
$(ExternalsDir)tk-$(TkVersion)\
$(ExternalsDir)tcltk-$(TclVersion)\$(ArchName)\
t
diff --git a/Tools/msi/tcltk/tcltk_files.wxs b/Tools/msi/tcltk/tcltk_files.wxs
index 5dad7c98d4f048..7c7784741d9178 100644
--- a/Tools/msi/tcltk/tcltk_files.wxs
+++ b/Tools/msi/tcltk/tcltk_files.wxs
@@ -10,11 +10,14 @@
-
-
+
+
-
-
+
+
+
+
+
diff --git a/Tools/msi/testrelease.bat b/Tools/msi/testrelease.bat
index 02bcca943cf79b..db98f690151196 100644
--- a/Tools/msi/testrelease.bat
+++ b/Tools/msi/testrelease.bat
@@ -88,9 +88,7 @@ exit /B 0
)
@if not errorlevel 1 (
@echo Testing Tcl/tk
- @set TCL_LIBRARY=%~2\Python\tcl\tcl8.6
"%~2\Python\python.exe" -m test -uall -v test_ttk_guionly test_tk test_idle > "%~2\tcltk.txt" 2>&1
- @set TCL_LIBRARY=
)
@set EXITCODE=%ERRORLEVEL%