File CVE-2025-6075-expandvars-perf-degrad.patch of Package python3.42038

From 8b8e68d3dc95f454f58fdd8aac10848facb1491d Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Fri, 31 Oct 2025 15:49:51 +0200
Subject: [PATCH 1/2] [3.9] gh-136065: Fix quadratic complexity in
 os.path.expandvars() (GH-134952) (cherry picked from commit
 f029e8db626ddc6e3a3beea4eff511a71aaceb5c)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
---
 Lib/ntpath.py                                                            |  404 +++---
 Lib/posixpath.py                                                         |  276 ++--
 Lib/test/test_genericpath.py                                             |   18 
 Lib/test/test_ntpath.py                                                  |  653 +++++-----
 Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst |    1 
 5 files changed, 771 insertions(+), 581 deletions(-)
 create mode 100644 Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst

Index: Python-3.6.15/Lib/ntpath.py
===================================================================
--- Python-3.6.15.orig/Lib/ntpath.py	2025-12-15 22:44:15.347462599 +0100
+++ Python-3.6.15/Lib/ntpath.py	2025-12-15 22:54:52.589626377 +0100
@@ -8,14 +8,14 @@
 # strings representing various path-related bits and pieces
 # These are primarily for export; internally, they are hardcoded.
 # Should be set before imports for resolving cyclic dependency.
-curdir = '.'
-pardir = '..'
-extsep = '.'
-sep = '\\'
-pathsep = ';'
-altsep = '/'
-defpath = '.;C:\\bin'
-devnull = 'nul'
+curdir = "."
+pardir = ".."
+extsep = "."
+sep = "\\"
+pathsep = ";"
+altsep = "/"
+defpath = ".;C:\\bin"
+devnull = "nul"
 
 import os
 import sys
@@ -23,24 +23,61 @@
 import genericpath
 from genericpath import *
 
-__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
-           "basename","dirname","commonprefix","getsize","getmtime",
-           "getatime","getctime", "islink","exists","lexists","isdir","isfile",
-           "ismount", "expanduser","expandvars","normpath","abspath",
-           "splitunc","curdir","pardir","sep","pathsep","defpath","altsep",
-           "extsep","devnull","realpath","supports_unicode_filenames","relpath",
-           "samefile", "sameopenfile", "samestat", "commonpath"]
+__all__ = [
+    "normcase",
+    "isabs",
+    "join",
+    "splitdrive",
+    "split",
+    "splitext",
+    "basename",
+    "dirname",
+    "commonprefix",
+    "getsize",
+    "getmtime",
+    "getatime",
+    "getctime",
+    "islink",
+    "exists",
+    "lexists",
+    "isdir",
+    "isfile",
+    "ismount",
+    "expanduser",
+    "expandvars",
+    "normpath",
+    "abspath",
+    "splitunc",
+    "curdir",
+    "pardir",
+    "sep",
+    "pathsep",
+    "defpath",
+    "altsep",
+    "extsep",
+    "devnull",
+    "realpath",
+    "supports_unicode_filenames",
+    "relpath",
+    "samefile",
+    "sameopenfile",
+    "samestat",
+    "commonpath",
+]
+
 
 def _get_bothseps(path):
     if isinstance(path, bytes):
-        return b'\\/'
+        return b"\\/"
     else:
-        return '\\/'
+        return "\\/"
+
 
 # Normalize the case of a pathname and map slashes to backslashes.
 # Other normalizations (such as optimizing '../' away) are not done
 # (this is done by normpath).
 
+
 def normcase(s):
     """Normalize case of pathname.
 
@@ -48,13 +85,15 @@
     s = os.fspath(s)
     try:
         if isinstance(s, bytes):
-            return s.replace(b'/', b'\\').lower()
+            return s.replace(b"/", b"\\").lower()
         else:
-            return s.replace('/', '\\').lower()
+            return s.replace("/", "\\").lower()
     except (TypeError, AttributeError):
         if not isinstance(s, (bytes, str)):
-            raise TypeError("normcase() argument must be str or bytes, "
-                            "not %r" % s.__class__.__name__) from None
+            raise TypeError(
+                "normcase() argument must be str or bytes, "
+                "not %r" % s.__class__.__name__
+            ) from None
         raise
 
 
@@ -64,6 +103,7 @@
 # volume), or if a pathname after the volume-letter-and-colon or UNC-resource
 # starts with a slash or backslash.
 
+
 def isabs(s):
     """Test whether a path is absolute"""
     s = os.fspath(s)
@@ -75,16 +115,16 @@
 def join(path, *paths):
     path = os.fspath(path)
     if isinstance(path, bytes):
-        sep = b'\\'
-        seps = b'\\/'
-        colon = b':'
+        sep = b"\\"
+        seps = b"\\/"
+        colon = b":"
     else:
-        sep = '\\'
-        seps = '\\/'
-        colon = ':'
+        sep = "\\"
+        seps = "\\/"
+        colon = ":"
     try:
         if not paths:
-            path[:0] + sep  #23780: Ensure compatible data type even if p is null.
+            path[:0] + sep  # 23780: Ensure compatible data type even if p is null.
         result_drive, result_path = splitdrive(path)
         for p in map(os.fspath, paths):
             p_drive, p_path = splitdrive(p)
@@ -107,12 +147,16 @@
                 result_path = result_path + sep
             result_path = result_path + p_path
         ## add separator between UNC and non-absolute path
-        if (result_path and result_path[0] not in seps and
-            result_drive and result_drive[-1:] != colon):
+        if (
+            result_path
+            and result_path[0] not in seps
+            and result_drive
+            and result_drive[-1:] != colon
+        ):
             return result_drive + sep + result_path
         return result_drive + result_path
     except (TypeError, AttributeError, BytesWarning):
-        genericpath._check_arg_types('join', path, *paths)
+        genericpath._check_arg_types("join", path, *paths)
         raise
 
 
@@ -141,15 +185,15 @@
     p = os.fspath(p)
     if len(p) >= 2:
         if isinstance(p, bytes):
-            sep = b'\\'
-            altsep = b'/'
-            colon = b':'
+            sep = b"\\"
+            altsep = b"/"
+            colon = b":"
         else:
-            sep = '\\'
-            altsep = '/'
-            colon = ':'
+            sep = "\\"
+            altsep = "/"
+            colon = ":"
         normp = p.replace(altsep, sep)
-        if (normp[0:2] == sep*2) and (normp[2:3] != sep):
+        if (normp[0:2] == sep * 2) and (normp[2:3] != sep):
             # is a UNC path:
             # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
             # \\machine\mountpoint\directory\etc\...
@@ -183,11 +227,15 @@
     Paths containing drive letters never have a UNC part.
     """
     import warnings
-    warnings.warn("ntpath.splitunc is deprecated, use ntpath.splitdrive instead",
-                  DeprecationWarning, 2)
+
+    warnings.warn(
+        "ntpath.splitunc is deprecated, use ntpath.splitdrive instead",
+        DeprecationWarning,
+        2,
+    )
     drive, path = splitdrive(p)
     if len(drive) == 2:
-         # Drive letter present
+        # Drive letter present
         return p[:0], p
     return drive, path
 
@@ -197,6 +245,7 @@
 # join(head, tail) == p holds.
 # The resulting head won't end in '/' unless it is the root.
 
+
 def split(p):
     """Split a pathname.
 
@@ -207,7 +256,7 @@
     d, p = splitdrive(p)
     # set i to index beyond p's last slash
     i = len(p)
-    while i and p[i-1] not in seps:
+    while i and p[i - 1] not in seps:
         i -= 1
     head, tail = p[:i], p[i:]  # now tail has no slashes
     # remove trailing slashes from head, unless it's all slashes
@@ -220,17 +269,21 @@
 # pathname component; the root is everything before that.
 # It is always true that root + ext == p.
 
+
 def splitext(p):
     p = os.fspath(p)
     if isinstance(p, bytes):
-        return genericpath._splitext(p, b'\\', b'/', b'.')
+        return genericpath._splitext(p, b"\\", b"/", b".")
     else:
-        return genericpath._splitext(p, '\\', '/', '.')
+        return genericpath._splitext(p, "\\", "/", ".")
+
+
 splitext.__doc__ = genericpath._splitext.__doc__
 
 
 # Return the tail (basename) part of a path.
 
+
 def basename(p):
     """Returns the final component of a pathname"""
     return split(p)[1]
@@ -238,13 +291,16 @@
 
 # Return the head (dirname) part of a path.
 
+
 def dirname(p):
     """Returns the directory component of a pathname"""
     return split(p)[0]
 
+
 # Is a path a symbolic link?
 # This will always return false on systems where os.lstat doesn't exist.
 
+
 def islink(path):
     """Test whether a path is a symbolic link.
     This will always return false for Windows prior to 6.0.
@@ -255,8 +311,10 @@
         return False
     return stat.S_ISLNK(st.st_mode)
 
+
 # Being true for dangling symbolic links is also useful.
 
+
 def lexists(path):
     """Test whether a path exists.  Returns True for broken symbolic links"""
     try:
@@ -265,6 +323,7 @@
         return False
     return True
 
+
 # Is a path a mount point?
 # Any drive letter root (eg c:\)
 # Any share UNC (eg \\server\share)
@@ -279,6 +338,8 @@
     from nt import _getvolumepathname
 except ImportError:
     _getvolumepathname = None
+
+
 def ismount(path):
     """Test whether a path is a mount point (a drive root, the root of a
     share, or a mounted volume)"""
@@ -306,38 +367,39 @@
 # (A function should also be defined to do full *sh-style environment
 # variable expansion.)
 
+
 def expanduser(path):
     """Expand ~ and ~user constructs.
 
     If user or $HOME is unknown, do nothing."""
     path = os.fspath(path)
     if isinstance(path, bytes):
-        tilde = b'~'
+        tilde = b"~"
     else:
-        tilde = '~'
+        tilde = "~"
     if not path.startswith(tilde):
         return path
     i, n = 1, len(path)
     while i < n and path[i] not in _get_bothseps(path):
         i += 1
 
-    if 'HOME' in os.environ:
-        userhome = os.environ['HOME']
-    elif 'USERPROFILE' in os.environ:
-        userhome = os.environ['USERPROFILE']
-    elif not 'HOMEPATH' in os.environ:
+    if "HOME" in os.environ:
+        userhome = os.environ["HOME"]
+    elif "USERPROFILE" in os.environ:
+        userhome = os.environ["USERPROFILE"]
+    elif not "HOMEPATH" in os.environ:
         return path
     else:
         try:
-            drive = os.environ['HOMEDRIVE']
+            drive = os.environ["HOMEDRIVE"]
         except KeyError:
-            drive = ''
-        userhome = join(drive, os.environ['HOMEPATH'])
+            drive = ""
+        userhome = join(drive, os.environ["HOMEPATH"])
 
     if isinstance(path, bytes):
         userhome = os.fsencode(userhome)
 
-    if i != 1: #~user
+    if i != 1:  # ~user
         userhome = join(dirname(userhome), path[1:i])
 
     return userhome + path[i:]
@@ -356,134 +418,100 @@
 # XXX With COMMAND.COM you can use any characters in a variable name,
 # XXX except '^|<>='.
 
+_varpattern = r"'[^']*'?|%(%|[^%]*%?)|\$(\$|[-\w]+|\{[^}]*\}?)"
+_varsub = None
+_varsubb = None
+
+
 def expandvars(path):
     """Expand shell variables of the forms $var, ${var} and %var%.
 
     Unknown variables are left unchanged."""
+    global _varsub, _varsubb
     path = os.fspath(path)
     if isinstance(path, bytes):
-        if b'$' not in path and b'%' not in path:
+        if b"$" not in path and b"%" not in path:
             return path
         import string
-        varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
-        quote = b'\''
-        percent = b'%'
-        brace = b'{'
-        rbrace = b'}'
-        dollar = b'$'
-        environ = getattr(os, 'environb', None)
+
+        if not _varsubb:
+            import re
+
+            _varsubb = re.compile(_varpattern.encode(), re.ASCII).sub
+        sub = _varsubb
+        percent = b"%"
+        brace = b"{"
+        rbrace = b"}"
+        dollar = b"$"
+        environ = getattr(os, "environb", None)
     else:
-        if '$' not in path and '%' not in path:
+        if "$" not in path and "%" not in path:
             return path
         import string
-        varchars = string.ascii_letters + string.digits + '_-'
-        quote = '\''
-        percent = '%'
-        brace = '{'
-        rbrace = '}'
-        dollar = '$'
+
+        if not _varsub:
+            import re
+
+            _varsub = re.compile(_varpattern, re.ASCII).sub
+        sub = _varsub
+        varchars = string.ascii_letters + string.digits + "_-"
+        quote = "'"
+        percent = "%"
+        brace = "{"
+        rbrace = "}"
+        dollar = "$"
         environ = os.environ
-    res = path[:0]
-    index = 0
-    pathlen = len(path)
-    while index < pathlen:
-        c = path[index:index+1]
-        if c == quote:   # no expansion within single quotes
-            path = path[index + 1:]
-            pathlen = len(path)
-            try:
-                index = path.index(c)
-                res += c + path[:index + 1]
-            except ValueError:
-                res += c + path
-                index = pathlen - 1
-        elif c == percent:  # variable or '%'
-            if path[index + 1:index + 2] == percent:
-                res += c
-                index += 1
-            else:
-                path = path[index+1:]
-                pathlen = len(path)
-                try:
-                    index = path.index(percent)
-                except ValueError:
-                    res += percent + path
-                    index = pathlen - 1
-                else:
-                    var = path[:index]
-                    try:
-                        if environ is None:
-                            value = os.fsencode(os.environ[os.fsdecode(var)])
-                        else:
-                            value = environ[var]
-                    except KeyError:
-                        value = percent + var + percent
-                    res += value
-        elif c == dollar:  # variable or '$$'
-            if path[index + 1:index + 2] == dollar:
-                res += c
-                index += 1
-            elif path[index + 1:index + 2] == brace:
-                path = path[index+2:]
-                pathlen = len(path)
-                try:
-                    index = path.index(rbrace)
-                except ValueError:
-                    res += dollar + brace + path
-                    index = pathlen - 1
-                else:
-                    var = path[:index]
-                    try:
-                        if environ is None:
-                            value = os.fsencode(os.environ[os.fsdecode(var)])
-                        else:
-                            value = environ[var]
-                    except KeyError:
-                        value = dollar + brace + var + rbrace
-                    res += value
-            else:
-                var = path[:0]
-                index += 1
-                c = path[index:index + 1]
-                while c and c in varchars:
-                    var += c
-                    index += 1
-                    c = path[index:index + 1]
-                try:
-                    if environ is None:
-                        value = os.fsencode(os.environ[os.fsdecode(var)])
-                    else:
-                        value = environ[var]
-                except KeyError:
-                    value = dollar + var
-                res += value
-                if c:
-                    index -= 1
+
+    def repl(m):
+        lastindex = m.lastindex
+        if lastindex is None:
+            return m.group(0)
+        name = m.group(lastindex)
+        if lastindex == 1:
+            if name == percent:
+                return name
+            if not name.endswith(percent):
+                return m.group(0)
+            name = name[:-1]
         else:
-            res += c
-        index += 1
-    return res
+            if name == dollar:
+                return name
+            if name.startswith(brace):
+                if not name.endswith(rbrace):
+                    return m.group(0)
+                name = name[1:-1]
+
+        try:
+            if environ is None:
+                return os.fsencode(os.environ[os.fsdecode(name)])
+            else:
+                return environ[name]
+        except KeyError:
+            return m.group(0)
+
+    return sub(repl, path)
 
 
 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
 # Previously, this function also truncated pathnames to 8+3 format,
 # but as this module is called "ntpath", that's obviously wrong!
 
+
 def normpath(path):
     """Normalize path, eliminating double slashes, etc."""
     path = os.fspath(path)
     if isinstance(path, bytes):
-        sep = b'\\'
-        altsep = b'/'
-        curdir = b'.'
-        pardir = b'..'
-        special_prefixes = (b'\\\\.\\', b'\\\\?\\')
+        sep = b"\\"
+        altsep = b"/"
+        curdir = b"."
+        pardir = b".."
+        special_prefixes = (b"\\\\.\\", b"\\\\?\\")
     else:
-        sep = '\\'
-        altsep = '/'
-        curdir = '.'
-        pardir = '..'
-        special_prefixes = ('\\\\.\\', '\\\\?\\')
+        sep = "\\"
+        altsep = "/"
+        curdir = "."
+        pardir = ".."
+        special_prefixes = ("\\\\.\\", "\\\\?\\")
     if path.startswith(special_prefixes):
         # in the case of paths with these prefixes:
         # \\.\ -> device names
@@ -504,8 +532,8 @@
         if not comps[i] or comps[i] == curdir:
             del comps[i]
         elif comps[i] == pardir:
-            if i > 0 and comps[i-1] != pardir:
-                del comps[i-1:i+1]
+            if i > 0 and comps[i - 1] != pardir:
+                del comps[i - 1 : i + 1]
                 i -= 1
             elif i == 0 and prefix.endswith(sep):
                 del comps[i]
@@ -518,6 +546,7 @@
         comps.append(curdir)
     return prefix + sep.join(comps)
 
+
 def _abspath_fallback(path):
     """Return the absolute version of a path as a fallback function in case
     `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for
@@ -534,14 +563,16 @@
         path = join(cwd, path)
     return normpath(path)
 
+
 # Return an absolute path.
 try:
     from nt import _getfullpathname
 
-except ImportError: # not running on Windows - mock up something sensible
+except ImportError:  # not running on Windows - mock up something sensible
     abspath = _abspath_fallback
 
 else:  # use native Windows method on Windows
+
     def abspath(path):
         """Return the absolute version of a path."""
         try:
@@ -549,23 +580,26 @@
         except (OSError, ValueError):
             return _abspath_fallback(path)
 
+
 # realpath is a no-op on systems without islink support
 realpath = abspath
 # Win9x family and earlier have no Unicode filename support.
-supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
-                              sys.getwindowsversion()[3] >= 2)
+supports_unicode_filenames = (
+    hasattr(sys, "getwindowsversion") and sys.getwindowsversion()[3] >= 2
+)
+
 
 def relpath(path, start=None):
     """Return a relative version of a path"""
     path = os.fspath(path)
     if isinstance(path, bytes):
-        sep = b'\\'
-        curdir = b'.'
-        pardir = b'..'
+        sep = b"\\"
+        curdir = b"."
+        pardir = b".."
     else:
-        sep = '\\'
-        curdir = '.'
-        pardir = '..'
+        sep = "\\"
+        curdir = "."
+        pardir = ".."
 
     if start is None:
         start = curdir
@@ -580,8 +614,9 @@
         start_drive, start_rest = splitdrive(start_abs)
         path_drive, path_rest = splitdrive(path_abs)
         if normcase(start_drive) != normcase(path_drive):
-            raise ValueError("path is on mount %r, start on mount %r" % (
-                path_drive, start_drive))
+            raise ValueError(
+                "path is on mount %r, start on mount %r" % (path_drive, start_drive)
+            )
 
         start_list = [x for x in start_rest.split(sep) if x]
         path_list = [x for x in path_rest.split(sep) if x]
@@ -592,12 +627,12 @@
                 break
             i += 1
 
-        rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
+        rel_list = [pardir] * (len(start_list) - i) + path_list[i:]
         if not rel_list:
             return curdir
         return join(*rel_list)
     except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning):
-        genericpath._check_arg_types('relpath', path, start)
+        genericpath._check_arg_types("relpath", path, start)
         raise
 
 
@@ -611,28 +646,29 @@
 # first path given in the sequence. Additionally, any trailing separator is
 # stripped from the returned path.
 
+
 def commonpath(paths):
     """Given a sequence of path names, returns the longest common sub-path."""
 
     if not paths:
-        raise ValueError('commonpath() arg is an empty sequence')
+        raise ValueError("commonpath() arg is an empty sequence")
 
     paths = tuple(map(os.fspath, paths))
     if isinstance(paths[0], bytes):
-        sep = b'\\'
-        altsep = b'/'
-        curdir = b'.'
+        sep = b"\\"
+        altsep = b"/"
+        curdir = b"."
     else:
-        sep = '\\'
-        altsep = '/'
-        curdir = '.'
+        sep = "\\"
+        altsep = "/"
+        curdir = "."
 
     try:
         drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths]
         split_paths = [p.split(sep) for d, p in drivesplits]
 
         try:
-            isabs, = set(p[:1] == sep for d, p in drivesplits)
+            (isabs,) = set(p[:1] == sep for d, p in drivesplits)
         except ValueError:
             raise ValueError("Can't mix absolute and relative paths") from None
 
@@ -654,12 +690,12 @@
                 common = common[:i]
                 break
         else:
-            common = common[:len(s1)]
+            common = common[: len(s1)]
 
         prefix = drive + sep if isabs else drive
         return prefix + sep.join(common)
     except (TypeError, AttributeError):
-        genericpath._check_arg_types('commonpath', *paths)
+        genericpath._check_arg_types("commonpath", *paths)
         raise
 
 
Index: Python-3.6.15/Lib/posixpath.py
===================================================================
--- Python-3.6.15.orig/Lib/posixpath.py	2025-12-15 22:47:08.210375357 +0100
+++ Python-3.6.15/Lib/posixpath.py	2025-12-15 22:57:11.623135544 +0100
@@ -13,14 +13,14 @@
 # Strings representing various path-related bits and pieces.
 # These are primarily for export; internally, they are hardcoded.
 # Should be set before imports for resolving cyclic dependency.
-curdir = '.'
-pardir = '..'
-extsep = '.'
-sep = '/'
-pathsep = ':'
-defpath = ':/bin:/usr/bin'
+curdir = "."
+pardir = ".."
+extsep = "."
+sep = "/"
+pathsep = ":"
+defpath = ":/bin:/usr/bin"
 altsep = None
-devnull = '/dev/null'
+devnull = "/dev/null"
 
 import os
 import sys
@@ -28,39 +28,77 @@
 import genericpath
 from genericpath import *
 
-__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
-           "basename","dirname","commonprefix","getsize","getmtime",
-           "getatime","getctime","islink","exists","lexists","isdir","isfile",
-           "ismount", "expanduser","expandvars","normpath","abspath",
-           "samefile","sameopenfile","samestat",
-           "curdir","pardir","sep","pathsep","defpath","altsep","extsep",
-           "devnull","realpath","supports_unicode_filenames","relpath",
-           "commonpath", "ALLOW_MISSING"]
+__all__ = [
+    "normcase",
+    "isabs",
+    "join",
+    "splitdrive",
+    "split",
+    "splitext",
+    "basename",
+    "dirname",
+    "commonprefix",
+    "getsize",
+    "getmtime",
+    "getatime",
+    "getctime",
+    "islink",
+    "exists",
+    "lexists",
+    "isdir",
+    "isfile",
+    "ismount",
+    "expanduser",
+    "expandvars",
+    "normpath",
+    "abspath",
+    "samefile",
+    "sameopenfile",
+    "samestat",
+    "curdir",
+    "pardir",
+    "sep",
+    "pathsep",
+    "defpath",
+    "altsep",
+    "extsep",
+    "devnull",
+    "realpath",
+    "supports_unicode_filenames",
+    "relpath",
+    "commonpath",
+    "ALLOW_MISSING",
+]
 
 
 def _get_sep(path):
     if isinstance(path, bytes):
-        return b'/'
+        return b"/"
     else:
-        return '/'
+        return "/"
+
 
 # Normalize the case of a pathname.  Trivial in Posix, string.lower on Mac.
 # On MS-DOS this may also turn slashes into backslashes; however, other
 # normalizations (such as optimizing '../' away) are not allowed
 # (another function should be defined to do that).
 
+
 def normcase(s):
     """Normalize case of pathname.  Has no effect under Posix"""
     s = os.fspath(s)
     if not isinstance(s, (bytes, str)):
-        raise TypeError("normcase() argument must be str or bytes, "
-                        "not '{}'".format(s.__class__.__name__))
+        raise TypeError(
+            "normcase() argument must be str or bytes, "
+            "not '{}'".format(s.__class__.__name__)
+        )
     return s
 
 
 # Return whether a path is absolute.
 # Trivial in Posix, harder on the Mac or MS-DOS.
 
+
 def isabs(s):
     """Test whether a path is absolute"""
     s = os.fspath(s)
@@ -72,6 +110,7 @@
 # Ignore the previous parts if a part is absolute.
 # Insert a '/' unless the first part is empty or already ends in '/'.
 
+
 def join(a, *p):
     """Join two or more pathname components, inserting '/' as needed.
     If any component is an absolute path, all previous path components
@@ -82,7 +121,7 @@
     path = a
     try:
         if not p:
-            path[:0] + sep  #23780: Ensure compatible data type even if p is null.
+            path[:0] + sep  # 23780: Ensure compatible data type even if p is null.
         for b in map(os.fspath, p):
             if b.startswith(sep):
                 path = b
@@ -91,7 +130,7 @@
             else:
                 path += sep + b
     except (TypeError, AttributeError, BytesWarning):
-        genericpath._check_arg_types('join', a, *p)
+        genericpath._check_arg_types("join", a, *p)
         raise
     return path
 
@@ -101,6 +140,7 @@
 # '/' in the path, head  will be empty.
 # Trailing '/'es are stripped from head unless it is the root.
 
+
 def split(p):
     """Split a pathname.  Returns tuple "(head, tail)" where "tail" is
     everything after the final slash.  Either part may be empty."""
@@ -108,7 +148,7 @@
     sep = _get_sep(p)
     i = p.rfind(sep) + 1
     head, tail = p[:i], p[i:]
-    if head and head != sep*len(head):
+    if head and head != sep * len(head):
         head = head.rstrip(sep)
     return head, tail
 
@@ -118,20 +158,24 @@
 # pathname component; the root is everything before that.
 # It is always true that root + ext == p.
 
+
 def splitext(p):
     p = os.fspath(p)
     if isinstance(p, bytes):
-        sep = b'/'
-        extsep = b'.'
+        sep = b"/"
+        extsep = b"."
     else:
-        sep = '/'
-        extsep = '.'
+        sep = "/"
+        extsep = "."
     return genericpath._splitext(p, sep, None, extsep)
+
+
 splitext.__doc__ = genericpath._splitext.__doc__
 
 # Split a pathname into a drive specification and the rest of the
 # path.  Useful on DOS/Windows/NT; on Unix, the drive is always empty.
 
+
 def splitdrive(p):
     """Split a pathname into drive and path. On Posix, drive is always
     empty."""
@@ -141,6 +185,7 @@
 
 # Return the tail (basename) part of a path, same as split(path)[1].
 
+
 def basename(p):
     """Returns the final component of a pathname"""
     p = os.fspath(p)
@@ -151,13 +196,14 @@
 
 # Return the head (dirname) part of a path, same as split(path)[0].
 
+
 def dirname(p):
     """Returns the directory component of a pathname"""
     p = os.fspath(p)
     sep = _get_sep(p)
     i = p.rfind(sep) + 1
     head = p[:i]
-    if head and head != sep*len(head):
+    if head and head != sep * len(head):
         head = head.rstrip(sep)
     return head
 
@@ -165,6 +211,7 @@
 # Is a path a symbolic link?
 # This will always return false on systems where os.lstat doesn't exist.
 
+
 def islink(path):
     """Test whether a path is a symbolic link"""
     try:
@@ -173,8 +220,10 @@
         return False
     return stat.S_ISLNK(st.st_mode)
 
+
 # Being true for dangling symbolic links is also useful.
 
+
 def lexists(path):
     """Test whether a path exists.  Returns True for broken symbolic links"""
     try:
@@ -187,6 +236,7 @@
 # Is a path a mount point?
 # (Does this work for all UNIXes?  Is it even guaranteed to work by Posix?)
 
+
 def ismount(path):
     """Test whether a path is a mount point"""
     try:
@@ -200,9 +250,9 @@
             return False
 
     if isinstance(path, bytes):
-        parent = join(path, b'..')
+        parent = join(path, b"..")
     else:
-        parent = join(path, '..')
+        parent = join(path, "..")
     parent = realpath(parent)
     try:
         s2 = os.lstat(parent)
@@ -212,11 +262,11 @@
     dev1 = s1.st_dev
     dev2 = s2.st_dev
     if dev1 != dev2:
-        return True     # path/.. on a different device as path
+        return True  # path/.. on a different device as path
     ino1 = s1.st_ino
     ino2 = s2.st_ino
     if ino1 == ino2:
-        return True     # path/.. is the same i-node as path
+        return True  # path/.. is the same i-node as path
     return False
 
 
@@ -229,14 +279,15 @@
 # (A function should also be defined to do full *sh-style environment
 # variable expansion.)
 
+
 def expanduser(path):
     """Expand ~ and ~user constructions.  If user or $HOME is unknown,
     do nothing."""
     path = os.fspath(path)
     if isinstance(path, bytes):
-        tilde = b'~'
+        tilde = b"~"
     else:
-        tilde = '~'
+        tilde = "~"
     if not path.startswith(tilde):
         return path
     sep = _get_sep(path)
@@ -244,8 +295,9 @@
     if i < 0:
         i = len(path)
     if i == 1:
-        if 'HOME' not in os.environ:
+        if "HOME" not in os.environ:
             import pwd
+
             try:
                 userhome = pwd.getpwuid(os.getuid()).pw_dir
             except KeyError:
@@ -253,12 +305,13 @@
                 # password database, return the path unchanged
                 return path
         else:
-            userhome = os.environ['HOME']
+            userhome = os.environ["HOME"]
     else:
         import pwd
+
         name = path[1:i]
         if isinstance(name, bytes):
-            name = str(name, 'ASCII')
+            name = str(name, "ASCII")
         try:
             pwent = pwd.getpwnam(name)
         except KeyError:
@@ -268,9 +321,9 @@
         userhome = pwent.pw_dir
     if isinstance(path, bytes):
         userhome = os.fsencode(userhome)
-        root = b'/'
+        root = b"/"
     else:
-        root = '/'
+        root = "/"
     userhome = userhome.rstrip(root)
     return (userhome + path[i:]) or root
 
@@ -279,42 +332,44 @@
 # This expands the forms $variable and ${variable} only.
 # Non-existent variables are left unchanged.
 
-_varprog = None
-_varprogb = None
+_varpattern = r"\$(\w+|\{[^}]*\}?)"
+_varsub = None
+_varsubb = None
+
 
 def expandvars(path):
     """Expand shell variables of form $var and ${var}.  Unknown variables
     are left unchanged."""
     path = os.fspath(path)
-    global _varprog, _varprogb
+    global _varsub, _varsubb
     if isinstance(path, bytes):
-        if b'$' not in path:
+        if b"$" not in path:
             return path
-        if not _varprogb:
+        if not _varsubb:
             import re
-            _varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII)
-        search = _varprogb.search
-        start = b'{'
-        end = b'}'
-        environ = getattr(os, 'environb', None)
+
+            _varsubb = re.compile(_varpattern.encode(), re.ASCII).sub
+        sub = _varsubb
+        start = b"{"
+        end = b"}"
+        environ = getattr(os, "environb", None)
     else:
-        if '$' not in path:
+        if "$" not in path:
             return path
-        if not _varprog:
+        if not _varsub:
             import re
-            _varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII)
-        search = _varprog.search
-        start = '{'
-        end = '}'
+
+            _varsub = re.compile(_varpattern, re.ASCII).sub
+        sub = _varsub
+        start = "{"
+        end = "}"
         environ = os.environ
-    i = 0
-    while True:
-        m = search(path, i)
-        if not m:
-            break
-        i, j = m.span(0)
+
+    def repl(m):
         name = m.group(1)
-        if name.startswith(start) and name.endswith(end):
+        if name.startswith(start):
+            if not name.endswith(end):
+                return m.group(0)
             name = name[1:-1]
         try:
             if environ is None:
@@ -322,54 +377,55 @@
             else:
                 value = environ[name]
         except KeyError:
-            i = j
+            return m.group(0)
         else:
-            tail = path[j:]
-            path = path[:i] + value
-            i = len(path)
-            path += tail
-    return path
+            return value
+
+    return sub(repl, path)
 
 
 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
 # It should be understood that this may change the meaning of the path
 # if it contains symbolic links!
 
+
 def normpath(path):
     """Normalize path, eliminating double slashes, etc."""
     path = os.fspath(path)
     if isinstance(path, bytes):
-        sep = b'/'
-        empty = b''
-        dot = b'.'
-        dotdot = b'..'
-    else:
-        sep = '/'
-        empty = ''
-        dot = '.'
-        dotdot = '..'
+        sep = b"/"
+        empty = b""
+        dot = b"."
+        dotdot = b".."
+    else:
+        sep = "/"
+        empty = ""
+        dot = "."
+        dotdot = ".."
     if path == empty:
         return dot
     initial_slashes = path.startswith(sep)
     # POSIX allows one or two initial slashes, but treats three or more
     # as single slash.
-    if (initial_slashes and
-        path.startswith(sep*2) and not path.startswith(sep*3)):
+    if initial_slashes and path.startswith(sep * 2) and not path.startswith(sep * 3):
         initial_slashes = 2
     comps = path.split(sep)
     new_comps = []
     for comp in comps:
         if comp in (empty, dot):
             continue
-        if (comp != dotdot or (not initial_slashes and not new_comps) or
-             (new_comps and new_comps[-1] == dotdot)):
+        if (
+            comp != dotdot
+            or (not initial_slashes and not new_comps)
+            or (new_comps and new_comps[-1] == dotdot)
+        ):
             new_comps.append(comp)
         elif new_comps:
             new_comps.pop()
     comps = new_comps
     path = sep.join(comps)
     if initial_slashes:
-        path = sep*initial_slashes + path
+        path = sep * initial_slashes + path
     return path or dot
 
 
@@ -388,24 +444,26 @@
 # Return a canonical path (i.e. the absolute location of a file on the
 # filesystem).
 
+
 def realpath(filename, *, strict=False):
     """Return the canonical path of the specified filename, eliminating any
-symbolic links encountered in the path."""
+    symbolic links encountered in the path."""
     filename = os.fspath(filename)
     path, ok = _joinrealpath(filename[:0], filename, strict, {})
     return abspath(path)
 
+
 # Join two paths, normalizing and eliminating any symbolic links
 # encountered in the second path.
 def _joinrealpath(path, rest, strict, seen):
     if isinstance(path, bytes):
-        sep = b'/'
-        curdir = b'.'
-        pardir = b'..'
-    else:
-        sep = '/'
-        curdir = '.'
-        pardir = '..'
+        sep = b"/"
+        curdir = b"."
+        pardir = b".."
+    else:
+        sep = "/"
+        curdir = "."
+        pardir = ".."
         getcwd = os.getcwd
     if strict is ALLOW_MISSING:
         ignored_error = FileNotFoundError
@@ -458,16 +516,17 @@
             else:
                 # Return already resolved part + rest of the path unchanged.
                 return join(newpath, rest), False
-        seen[newpath] = None # not resolved symlink
+        seen[newpath] = None  # not resolved symlink
         path, ok = _joinrealpath(path, os.readlink(newpath), strict, seen)
         if not ok:
             return join(path, rest), False
-        seen[newpath] = path # resolved symlink
+        seen[newpath] = path  # resolved symlink
 
     return path, True
 
 
-supports_unicode_filenames = (sys.platform == 'darwin')
+supports_unicode_filenames = sys.platform == "darwin"
+
 
 def relpath(path, start=None):
     """Return a relative version of a path"""
@@ -477,13 +536,13 @@
 
     path = os.fspath(path)
     if isinstance(path, bytes):
-        curdir = b'.'
-        sep = b'/'
-        pardir = b'..'
-    else:
-        curdir = '.'
-        sep = '/'
-        pardir = '..'
+        curdir = b"."
+        sep = b"/"
+        pardir = b".."
+    else:
+        curdir = "."
+        sep = "/"
+        pardir = ".."
 
     if start is None:
         start = curdir
@@ -496,12 +555,12 @@
         # Work out how much of the filepath is shared by start and path.
         i = len(commonprefix([start_list, path_list]))
 
-        rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
+        rel_list = [pardir] * (len(start_list) - i) + path_list[i:]
         if not rel_list:
             return curdir
         return join(*rel_list)
     except (TypeError, AttributeError, BytesWarning, DeprecationWarning):
-        genericpath._check_arg_types('relpath', path, start)
+        genericpath._check_arg_types("relpath", path, start)
         raise
 
 
@@ -510,25 +569,26 @@
 # responsibility of the caller). Any trailing separator is stripped from the
 # returned path.
 
+
 def commonpath(paths):
     """Given a sequence of path names, returns the longest common sub-path."""
 
     if not paths:
-        raise ValueError('commonpath() arg is an empty sequence')
+        raise ValueError("commonpath() arg is an empty sequence")
 
     paths = tuple(map(os.fspath, paths))
     if isinstance(paths[0], bytes):
-        sep = b'/'
-        curdir = b'.'
+        sep = b"/"
+        curdir = b"."
     else:
-        sep = '/'
-        curdir = '.'
+        sep = "/"
+        curdir = "."
 
     try:
         split_paths = [path.split(sep) for path in paths]
 
         try:
-            isabs, = set(p[:1] == sep for p in paths)
+            (isabs,) = set(p[:1] == sep for p in paths)
         except ValueError:
             raise ValueError("Can't mix absolute and relative paths") from None
 
@@ -544,5 +604,5 @@
         prefix = sep if isabs else sep[:0]
         return prefix + sep.join(common)
     except (TypeError, AttributeError):
-        genericpath._check_arg_types('commonpath', *paths)
+        genericpath._check_arg_types("commonpath", *paths)
         raise
Index: Python-3.6.15/Lib/test/test_genericpath.py
===================================================================
--- Python-3.6.15.orig/Lib/test/test_genericpath.py	2025-12-15 22:44:17.363776048 +0100
+++ Python-3.6.15/Lib/test/test_genericpath.py	2025-12-15 22:47:54.182942670 +0100
@@ -10,6 +10,7 @@
 from test import support
 from test.support.script_helper import assert_python_ok
 from test.support import FakePath
+from test.support import EnvironmentVarGuard
 
 
 def create_file(filename, data=b'foo'):
@@ -354,7 +355,7 @@
         if self.pathmodule.__name__ == 'macpath':
             self.skipTest('macpath.expandvars is a stub')
         expandvars = self.pathmodule.expandvars
-        with support.EnvironmentVarGuard() as env:
+        with EnvironmentVarGuard() as env:
             env.clear()
             env["foo"] = "bar"
             env["{foo"] = "baz1"
@@ -390,7 +391,7 @@
         expandvars = self.pathmodule.expandvars
         def check(value, expected):
             self.assertEqual(expandvars(value), expected)
-        with support.EnvironmentVarGuard() as env:
+        with EnvironmentVarGuard() as env:
             env.clear()
             nonascii = support.FS_NONASCII
             env['spam'] = nonascii
@@ -411,6 +412,19 @@
                   os.fsencode('$bar%s bar' % nonascii))
             check(b'$spam}bar', os.fsencode('%s}bar' % nonascii))
 
+    @support.requires_resource('cpu')
+    def test_expandvars_large(self):
+        expandvars = self.pathmodule.expandvars
+        with EnvironmentVarGuard() as env:
+            env.clear()
+            env["A"] = "B"
+            n = 100000
+            self.assertEqual(expandvars('$A'*n), 'B'*n)
+            self.assertEqual(expandvars('${A}'*n), 'B'*n)
+            self.assertEqual(expandvars('$A!'*n), 'B!'*n)
+            self.assertEqual(expandvars('${A}A'*n), 'BA'*n)
+            self.assertEqual(expandvars('${'*10*n), '${'*10*n)
+
     def test_abspath(self):
         self.assertIn("foo", self.pathmodule.abspath("foo"))
         with warnings.catch_warnings():
Index: Python-3.6.15/Lib/test/test_ntpath.py
===================================================================
--- Python-3.6.15.orig/Lib/test/test_ntpath.py	2025-12-15 22:47:08.211568204 +0100
+++ Python-3.6.15/Lib/test/test_ntpath.py	2025-12-15 22:48:53.187133106 +0100
@@ -5,7 +5,7 @@
 import unittest
 import warnings
 from ntpath import ALLOW_MISSING
-from test.support import TestFailed, FakePath
+from test.support import TestFailed, FakePath, EnvironmentVarGuard
 from test import support, test_genericpath
 from tempfile import TemporaryFile
 
@@ -14,8 +14,10 @@
     fn = fn.replace("\\", "\\\\")
     gotResult = eval(fn)
     if wantResult != gotResult:
-        raise TestFailed("%s should return: %s but returned: %s" \
-              %(str(fn), str(wantResult), str(gotResult)))
+        raise TestFailed(
+            "%s should return: %s but returned: %s"
+            % (str(fn), str(wantResult), str(gotResult))
+        )
 
     # then with bytes
     fn = fn.replace("('", "(b'")
@@ -24,8 +26,8 @@
     fn = fn.replace('["', '[b"')
     fn = fn.replace(", '", ", b'")
     fn = fn.replace(', "', ', b"')
-    fn = os.fsencode(fn).decode('latin1')
-    fn = fn.encode('ascii', 'backslashreplace').decode('ascii')
+    fn = os.fsencode(fn).decode("latin1")
+    fn = fn.encode("ascii", "backslashreplace").decode("ascii")
     with warnings.catch_warnings():
         warnings.simplefilter("ignore", DeprecationWarning)
         gotResult = eval(fn)
@@ -36,78 +38,107 @@
 
     gotResult = eval(fn)
     if wantResult != gotResult:
-        raise TestFailed("%s should return: %s but returned: %s" \
-              %(str(fn), str(wantResult), repr(gotResult)))
+        raise TestFailed(
+            "%s should return: %s but returned: %s"
+            % (str(fn), str(wantResult), repr(gotResult))
+        )
 
 
 class TestNtpath(unittest.TestCase):
     def test_splitext(self):
-        tester('ntpath.splitext("foo.ext")', ('foo', '.ext'))
-        tester('ntpath.splitext("/foo/foo.ext")', ('/foo/foo', '.ext'))
-        tester('ntpath.splitext(".ext")', ('.ext', ''))
-        tester('ntpath.splitext("\\foo.ext\\foo")', ('\\foo.ext\\foo', ''))
-        tester('ntpath.splitext("foo.ext\\")', ('foo.ext\\', ''))
-        tester('ntpath.splitext("")', ('', ''))
-        tester('ntpath.splitext("foo.bar.ext")', ('foo.bar', '.ext'))
-        tester('ntpath.splitext("xx/foo.bar.ext")', ('xx/foo.bar', '.ext'))
-        tester('ntpath.splitext("xx\\foo.bar.ext")', ('xx\\foo.bar', '.ext'))
-        tester('ntpath.splitext("c:a/b\\c.d")', ('c:a/b\\c', '.d'))
+        tester('ntpath.splitext("foo.ext")', ("foo", ".ext"))
+        tester('ntpath.splitext("/foo/foo.ext")', ("/foo/foo", ".ext"))
+        tester('ntpath.splitext(".ext")', (".ext", ""))
+        tester('ntpath.splitext("\\foo.ext\\foo")', ("\\foo.ext\\foo", ""))
+        tester('ntpath.splitext("foo.ext\\")', ("foo.ext\\", ""))
+        tester('ntpath.splitext("")', ("", ""))
+        tester('ntpath.splitext("foo.bar.ext")', ("foo.bar", ".ext"))
+        tester('ntpath.splitext("xx/foo.bar.ext")', ("xx/foo.bar", ".ext"))
+        tester('ntpath.splitext("xx\\foo.bar.ext")', ("xx\\foo.bar", ".ext"))
+        tester('ntpath.splitext("c:a/b\\c.d")', ("c:a/b\\c", ".d"))
 
     def test_splitdrive(self):
-        tester('ntpath.splitdrive("c:\\foo\\bar")',
-               ('c:', '\\foo\\bar'))
-        tester('ntpath.splitdrive("c:/foo/bar")',
-               ('c:', '/foo/bar'))
-        tester('ntpath.splitdrive("\\\\conky\\mountpoint\\foo\\bar")',
-               ('\\\\conky\\mountpoint', '\\foo\\bar'))
-        tester('ntpath.splitdrive("//conky/mountpoint/foo/bar")',
-               ('//conky/mountpoint', '/foo/bar'))
-        tester('ntpath.splitdrive("\\\\\\conky\\mountpoint\\foo\\bar")',
-            ('', '\\\\\\conky\\mountpoint\\foo\\bar'))
-        tester('ntpath.splitdrive("///conky/mountpoint/foo/bar")',
-            ('', '///conky/mountpoint/foo/bar'))
-        tester('ntpath.splitdrive("\\\\conky\\\\mountpoint\\foo\\bar")',
-               ('', '\\\\conky\\\\mountpoint\\foo\\bar'))
-        tester('ntpath.splitdrive("//conky//mountpoint/foo/bar")',
-               ('', '//conky//mountpoint/foo/bar'))
+        tester('ntpath.splitdrive("c:\\foo\\bar")', ("c:", "\\foo\\bar"))
+        tester('ntpath.splitdrive("c:/foo/bar")', ("c:", "/foo/bar"))
+        tester(
+            'ntpath.splitdrive("\\\\conky\\mountpoint\\foo\\bar")',
+            ("\\\\conky\\mountpoint", "\\foo\\bar"),
+        )
+        tester(
+            'ntpath.splitdrive("//conky/mountpoint/foo/bar")',
+            ("//conky/mountpoint", "/foo/bar"),
+        )
+        tester(
+            'ntpath.splitdrive("\\\\\\conky\\mountpoint\\foo\\bar")',
+            ("", "\\\\\\conky\\mountpoint\\foo\\bar"),
+        )
+        tester(
+            'ntpath.splitdrive("///conky/mountpoint/foo/bar")',
+            ("", "///conky/mountpoint/foo/bar"),
+        )
+        tester(
+            'ntpath.splitdrive("\\\\conky\\\\mountpoint\\foo\\bar")',
+            ("", "\\\\conky\\\\mountpoint\\foo\\bar"),
+        )
+        tester(
+            'ntpath.splitdrive("//conky//mountpoint/foo/bar")',
+            ("", "//conky//mountpoint/foo/bar"),
+        )
         # Issue #19911: UNC part containing U+0130
-        self.assertEqual(ntpath.splitdrive('//conky/MOUNTPOİNT/foo/bar'),
-                         ('//conky/MOUNTPOİNT', '/foo/bar'))
+        self.assertEqual(
+            ntpath.splitdrive("//conky/MOUNTPOİNT/foo/bar"),
+            ("//conky/MOUNTPOİNT", "/foo/bar"),
+        )
 
     def test_splitunc(self):
         with self.assertWarns(DeprecationWarning):
-            ntpath.splitunc('')
-        with support.check_warnings(('', DeprecationWarning)):
-            tester('ntpath.splitunc("c:\\foo\\bar")',
-                   ('', 'c:\\foo\\bar'))
-            tester('ntpath.splitunc("c:/foo/bar")',
-                   ('', 'c:/foo/bar'))
-            tester('ntpath.splitunc("\\\\conky\\mountpoint\\foo\\bar")',
-                   ('\\\\conky\\mountpoint', '\\foo\\bar'))
-            tester('ntpath.splitunc("//conky/mountpoint/foo/bar")',
-                   ('//conky/mountpoint', '/foo/bar'))
-            tester('ntpath.splitunc("\\\\\\conky\\mountpoint\\foo\\bar")',
-                   ('', '\\\\\\conky\\mountpoint\\foo\\bar'))
-            tester('ntpath.splitunc("///conky/mountpoint/foo/bar")',
-                   ('', '///conky/mountpoint/foo/bar'))
-            tester('ntpath.splitunc("\\\\conky\\\\mountpoint\\foo\\bar")',
-                   ('', '\\\\conky\\\\mountpoint\\foo\\bar'))
-            tester('ntpath.splitunc("//conky//mountpoint/foo/bar")',
-                   ('', '//conky//mountpoint/foo/bar'))
-            self.assertEqual(ntpath.splitunc('//conky/MOUNTPOİNT/foo/bar'),
-                             ('//conky/MOUNTPOİNT', '/foo/bar'))
+            ntpath.splitunc("")
+        with support.check_warnings(("", DeprecationWarning)):
+            tester('ntpath.splitunc("c:\\foo\\bar")', ("", "c:\\foo\\bar"))
+            tester('ntpath.splitunc("c:/foo/bar")', ("", "c:/foo/bar"))
+            tester(
+                'ntpath.splitunc("\\\\conky\\mountpoint\\foo\\bar")',
+                ("\\\\conky\\mountpoint", "\\foo\\bar"),
+            )
+            tester(
+                'ntpath.splitunc("//conky/mountpoint/foo/bar")',
+                ("//conky/mountpoint", "/foo/bar"),
+            )
+            tester(
+                'ntpath.splitunc("\\\\\\conky\\mountpoint\\foo\\bar")',
+                ("", "\\\\\\conky\\mountpoint\\foo\\bar"),
+            )
+            tester(
+                'ntpath.splitunc("///conky/mountpoint/foo/bar")',
+                ("", "///conky/mountpoint/foo/bar"),
+            )
+            tester(
+                'ntpath.splitunc("\\\\conky\\\\mountpoint\\foo\\bar")',
+                ("", "\\\\conky\\\\mountpoint\\foo\\bar"),
+            )
+            tester(
+                'ntpath.splitunc("//conky//mountpoint/foo/bar")',
+                ("", "//conky//mountpoint/foo/bar"),
+            )
+            self.assertEqual(
+                ntpath.splitunc("//conky/MOUNTPOİNT/foo/bar"),
+                ("//conky/MOUNTPOİNT", "/foo/bar"),
+            )
 
     def test_split(self):
-        tester('ntpath.split("c:\\foo\\bar")', ('c:\\foo', 'bar'))
-        tester('ntpath.split("\\\\conky\\mountpoint\\foo\\bar")',
-               ('\\\\conky\\mountpoint\\foo', 'bar'))
-
-        tester('ntpath.split("c:\\")', ('c:\\', ''))
-        tester('ntpath.split("\\\\conky\\mountpoint\\")',
-               ('\\\\conky\\mountpoint\\', ''))
+        tester('ntpath.split("c:\\foo\\bar")', ("c:\\foo", "bar"))
+        tester(
+            'ntpath.split("\\\\conky\\mountpoint\\foo\\bar")',
+            ("\\\\conky\\mountpoint\\foo", "bar"),
+        )
+
+        tester('ntpath.split("c:\\")', ("c:\\", ""))
+        tester(
+            'ntpath.split("\\\\conky\\mountpoint\\")', ("\\\\conky\\mountpoint\\", "")
+        )
 
-        tester('ntpath.split("c:/")', ('c:/', ''))
-        tester('ntpath.split("//conky/mountpoint/")', ('//conky/mountpoint/', ''))
+        tester('ntpath.split("c:/")', ("c:/", ""))
+        tester('ntpath.split("//conky/mountpoint/")', ("//conky/mountpoint/", ""))
 
     def test_isabs(self):
         tester('ntpath.isabs("c:\\")', 1)
@@ -116,107 +147,139 @@
         tester('ntpath.isabs("\\foo\\bar")', 1)
 
     def test_commonprefix(self):
-        tester('ntpath.commonprefix(["/home/swenson/spam", "/home/swen/spam"])',
-               "/home/swen")
-        tester('ntpath.commonprefix(["\\home\\swen\\spam", "\\home\\swen\\eggs"])',
-               "\\home\\swen\\")
-        tester('ntpath.commonprefix(["/home/swen/spam", "/home/swen/spam"])',
-               "/home/swen/spam")
+        tester(
+            'ntpath.commonprefix(["/home/swenson/spam", "/home/swen/spam"])',
+            "/home/swen",
+        )
+        tester(
+            'ntpath.commonprefix(["\\home\\swen\\spam", "\\home\\swen\\eggs"])',
+            "\\home\\swen\\",
+        )
+        tester(
+            'ntpath.commonprefix(["/home/swen/spam", "/home/swen/spam"])',
+            "/home/swen/spam",
+        )
 
     def test_join(self):
-        tester('ntpath.join("")', '')
-        tester('ntpath.join("", "", "")', '')
-        tester('ntpath.join("a")', 'a')
-        tester('ntpath.join("/a")', '/a')
-        tester('ntpath.join("\\a")', '\\a')
-        tester('ntpath.join("a:")', 'a:')
-        tester('ntpath.join("a:", "\\b")', 'a:\\b')
-        tester('ntpath.join("a", "\\b")', '\\b')
-        tester('ntpath.join("a", "b", "c")', 'a\\b\\c')
-        tester('ntpath.join("a\\", "b", "c")', 'a\\b\\c')
-        tester('ntpath.join("a", "b\\", "c")', 'a\\b\\c')
-        tester('ntpath.join("a", "b", "\\c")', '\\c')
-        tester('ntpath.join("d:\\", "\\pleep")', 'd:\\pleep')
-        tester('ntpath.join("d:\\", "a", "b")', 'd:\\a\\b')
-
-        tester("ntpath.join('', 'a')", 'a')
-        tester("ntpath.join('', '', '', '', 'a')", 'a')
-        tester("ntpath.join('a', '')", 'a\\')
-        tester("ntpath.join('a', '', '', '', '')", 'a\\')
-        tester("ntpath.join('a\\', '')", 'a\\')
-        tester("ntpath.join('a\\', '', '', '', '')", 'a\\')
-        tester("ntpath.join('a/', '')", 'a/')
-
-        tester("ntpath.join('a/b', 'x/y')", 'a/b\\x/y')
-        tester("ntpath.join('/a/b', 'x/y')", '/a/b\\x/y')
-        tester("ntpath.join('/a/b/', 'x/y')", '/a/b/x/y')
-        tester("ntpath.join('c:', 'x/y')", 'c:x/y')
-        tester("ntpath.join('c:a/b', 'x/y')", 'c:a/b\\x/y')
-        tester("ntpath.join('c:a/b/', 'x/y')", 'c:a/b/x/y')
-        tester("ntpath.join('c:/', 'x/y')", 'c:/x/y')
-        tester("ntpath.join('c:/a/b', 'x/y')", 'c:/a/b\\x/y')
-        tester("ntpath.join('c:/a/b/', 'x/y')", 'c:/a/b/x/y')
-        tester("ntpath.join('//computer/share', 'x/y')", '//computer/share\\x/y')
-        tester("ntpath.join('//computer/share/', 'x/y')", '//computer/share/x/y')
-        tester("ntpath.join('//computer/share/a/b', 'x/y')", '//computer/share/a/b\\x/y')
-
-        tester("ntpath.join('a/b', '/x/y')", '/x/y')
-        tester("ntpath.join('/a/b', '/x/y')", '/x/y')
-        tester("ntpath.join('c:', '/x/y')", 'c:/x/y')
-        tester("ntpath.join('c:a/b', '/x/y')", 'c:/x/y')
-        tester("ntpath.join('c:/', '/x/y')", 'c:/x/y')
-        tester("ntpath.join('c:/a/b', '/x/y')", 'c:/x/y')
-        tester("ntpath.join('//computer/share', '/x/y')", '//computer/share/x/y')
-        tester("ntpath.join('//computer/share/', '/x/y')", '//computer/share/x/y')
-        tester("ntpath.join('//computer/share/a', '/x/y')", '//computer/share/x/y')
-
-        tester("ntpath.join('c:', 'C:x/y')", 'C:x/y')
-        tester("ntpath.join('c:a/b', 'C:x/y')", 'C:a/b\\x/y')
-        tester("ntpath.join('c:/', 'C:x/y')", 'C:/x/y')
-        tester("ntpath.join('c:/a/b', 'C:x/y')", 'C:/a/b\\x/y')
-
-        for x in ('', 'a/b', '/a/b', 'c:', 'c:a/b', 'c:/', 'c:/a/b',
-                  '//computer/share', '//computer/share/', '//computer/share/a/b'):
-            for y in ('d:', 'd:x/y', 'd:/', 'd:/x/y',
-                      '//machine/common', '//machine/common/', '//machine/common/x/y'):
+        tester('ntpath.join("")', "")
+        tester('ntpath.join("", "", "")', "")
+        tester('ntpath.join("a")', "a")
+        tester('ntpath.join("/a")', "/a")
+        tester('ntpath.join("\\a")', "\\a")
+        tester('ntpath.join("a:")', "a:")
+        tester('ntpath.join("a:", "\\b")', "a:\\b")
+        tester('ntpath.join("a", "\\b")', "\\b")
+        tester('ntpath.join("a", "b", "c")', "a\\b\\c")
+        tester('ntpath.join("a\\", "b", "c")', "a\\b\\c")
+        tester('ntpath.join("a", "b\\", "c")', "a\\b\\c")
+        tester('ntpath.join("a", "b", "\\c")', "\\c")
+        tester('ntpath.join("d:\\", "\\pleep")', "d:\\pleep")
+        tester('ntpath.join("d:\\", "a", "b")', "d:\\a\\b")
+
+        tester("ntpath.join('', 'a')", "a")
+        tester("ntpath.join('', '', '', '', 'a')", "a")
+        tester("ntpath.join('a', '')", "a\\")
+        tester("ntpath.join('a', '', '', '', '')", "a\\")
+        tester("ntpath.join('a\\', '')", "a\\")
+        tester("ntpath.join('a\\', '', '', '', '')", "a\\")
+        tester("ntpath.join('a/', '')", "a/")
+
+        tester("ntpath.join('a/b', 'x/y')", "a/b\\x/y")
+        tester("ntpath.join('/a/b', 'x/y')", "/a/b\\x/y")
+        tester("ntpath.join('/a/b/', 'x/y')", "/a/b/x/y")
+        tester("ntpath.join('c:', 'x/y')", "c:x/y")
+        tester("ntpath.join('c:a/b', 'x/y')", "c:a/b\\x/y")
+        tester("ntpath.join('c:a/b/', 'x/y')", "c:a/b/x/y")
+        tester("ntpath.join('c:/', 'x/y')", "c:/x/y")
+        tester("ntpath.join('c:/a/b', 'x/y')", "c:/a/b\\x/y")
+        tester("ntpath.join('c:/a/b/', 'x/y')", "c:/a/b/x/y")
+        tester("ntpath.join('//computer/share', 'x/y')", "//computer/share\\x/y")
+        tester("ntpath.join('//computer/share/', 'x/y')", "//computer/share/x/y")
+        tester(
+            "ntpath.join('//computer/share/a/b', 'x/y')", "//computer/share/a/b\\x/y"
+        )
+
+        tester("ntpath.join('a/b', '/x/y')", "/x/y")
+        tester("ntpath.join('/a/b', '/x/y')", "/x/y")
+        tester("ntpath.join('c:', '/x/y')", "c:/x/y")
+        tester("ntpath.join('c:a/b', '/x/y')", "c:/x/y")
+        tester("ntpath.join('c:/', '/x/y')", "c:/x/y")
+        tester("ntpath.join('c:/a/b', '/x/y')", "c:/x/y")
+        tester("ntpath.join('//computer/share', '/x/y')", "//computer/share/x/y")
+        tester("ntpath.join('//computer/share/', '/x/y')", "//computer/share/x/y")
+        tester("ntpath.join('//computer/share/a', '/x/y')", "//computer/share/x/y")
+
+        tester("ntpath.join('c:', 'C:x/y')", "C:x/y")
+        tester("ntpath.join('c:a/b', 'C:x/y')", "C:a/b\\x/y")
+        tester("ntpath.join('c:/', 'C:x/y')", "C:/x/y")
+        tester("ntpath.join('c:/a/b', 'C:x/y')", "C:/a/b\\x/y")
+
+        for x in (
+            "",
+            "a/b",
+            "/a/b",
+            "c:",
+            "c:a/b",
+            "c:/",
+            "c:/a/b",
+            "//computer/share",
+            "//computer/share/",
+            "//computer/share/a/b",
+        ):
+            for y in (
+                "d:",
+                "d:x/y",
+                "d:/",
+                "d:/x/y",
+                "//machine/common",
+                "//machine/common/",
+                "//machine/common/x/y",
+            ):
                 tester("ntpath.join(%r, %r)" % (x, y), y)
 
-        tester("ntpath.join('\\\\computer\\share\\', 'a', 'b')", '\\\\computer\\share\\a\\b')
-        tester("ntpath.join('\\\\computer\\share', 'a', 'b')", '\\\\computer\\share\\a\\b')
-        tester("ntpath.join('\\\\computer\\share', 'a\\b')", '\\\\computer\\share\\a\\b')
-        tester("ntpath.join('//computer/share/', 'a', 'b')", '//computer/share/a\\b')
-        tester("ntpath.join('//computer/share', 'a', 'b')", '//computer/share\\a\\b')
-        tester("ntpath.join('//computer/share', 'a/b')", '//computer/share\\a/b')
+        tester(
+            "ntpath.join('\\\\computer\\share\\', 'a', 'b')",
+            "\\\\computer\\share\\a\\b",
+        )
+        tester(
+            "ntpath.join('\\\\computer\\share', 'a', 'b')", "\\\\computer\\share\\a\\b"
+        )
+        tester(
+            "ntpath.join('\\\\computer\\share', 'a\\b')", "\\\\computer\\share\\a\\b"
+        )
+        tester("ntpath.join('//computer/share/', 'a', 'b')", "//computer/share/a\\b")
+        tester("ntpath.join('//computer/share', 'a', 'b')", "//computer/share\\a\\b")
+        tester("ntpath.join('//computer/share', 'a/b')", "//computer/share\\a/b")
 
     def test_normpath(self):
-        tester("ntpath.normpath('A//////././//.//B')", r'A\B')
-        tester("ntpath.normpath('A/./B')", r'A\B')
-        tester("ntpath.normpath('A/foo/../B')", r'A\B')
-        tester("ntpath.normpath('C:A//B')", r'C:A\B')
-        tester("ntpath.normpath('D:A/./B')", r'D:A\B')
-        tester("ntpath.normpath('e:A/foo/../B')", r'e:A\B')
-
-        tester("ntpath.normpath('C:///A//B')", r'C:\A\B')
-        tester("ntpath.normpath('D:///A/./B')", r'D:\A\B')
-        tester("ntpath.normpath('e:///A/foo/../B')", r'e:\A\B')
-
-        tester("ntpath.normpath('..')", r'..')
-        tester("ntpath.normpath('.')", r'.')
-        tester("ntpath.normpath('')", r'.')
-        tester("ntpath.normpath('/')", '\\')
-        tester("ntpath.normpath('c:/')", 'c:\\')
-        tester("ntpath.normpath('/../.././..')", '\\')
-        tester("ntpath.normpath('c:/../../..')", 'c:\\')
-        tester("ntpath.normpath('../.././..')", r'..\..\..')
-        tester("ntpath.normpath('K:../.././..')", r'K:..\..\..')
-        tester("ntpath.normpath('C:////a/b')", r'C:\a\b')
-        tester("ntpath.normpath('//machine/share//a/b')", r'\\machine\share\a\b')
+        tester("ntpath.normpath('A//////././//.//B')", r"A\B")
+        tester("ntpath.normpath('A/./B')", r"A\B")
+        tester("ntpath.normpath('A/foo/../B')", r"A\B")
+        tester("ntpath.normpath('C:A//B')", r"C:A\B")
+        tester("ntpath.normpath('D:A/./B')", r"D:A\B")
+        tester("ntpath.normpath('e:A/foo/../B')", r"e:A\B")
+
+        tester("ntpath.normpath('C:///A//B')", r"C:\A\B")
+        tester("ntpath.normpath('D:///A/./B')", r"D:\A\B")
+        tester("ntpath.normpath('e:///A/foo/../B')", r"e:\A\B")
+
+        tester("ntpath.normpath('..')", r"..")
+        tester("ntpath.normpath('.')", r".")
+        tester("ntpath.normpath('')", r".")
+        tester("ntpath.normpath('/')", "\\")
+        tester("ntpath.normpath('c:/')", "c:\\")
+        tester("ntpath.normpath('/../.././..')", "\\")
+        tester("ntpath.normpath('c:/../../..')", "c:\\")
+        tester("ntpath.normpath('../.././..')", r"..\..\..")
+        tester("ntpath.normpath('K:../.././..')", r"K:..\..\..")
+        tester("ntpath.normpath('C:////a/b')", r"C:\a\b")
+        tester("ntpath.normpath('//machine/share//a/b')", r"\\machine\share\a\b")
 
-        tester("ntpath.normpath('\\\\.\\NUL')", r'\\.\NUL')
-        tester("ntpath.normpath('\\\\?\\D:/XY\\Z')", r'\\?\D:/XY\Z')
+        tester("ntpath.normpath('\\\\.\\NUL')", r"\\.\NUL")
+        tester("ntpath.normpath('\\\\?\\D:/XY\\Z')", r"\\?\D:/XY\Z")
 
     def test_expandvars(self):
-        with support.EnvironmentVarGuard() as env:
+        with EnvironmentVarGuard() as env:
             env.clear()
             env["foo"] = "bar"
             env["{foo"] = "baz1"
@@ -238,63 +301,73 @@
             tester('ntpath.expandvars("%%foo%%foo%foo%")', "%foo%foobar")
             tester('ntpath.expandvars("%?bar%")', "%?bar%")
             tester('ntpath.expandvars("%foo%%bar")', "bar%bar")
-            tester('ntpath.expandvars("\'%foo%\'%bar")', "\'%foo%\'%bar")
-            tester('ntpath.expandvars("bar\'%foo%")', "bar\'%foo%")
+            tester("ntpath.expandvars(\"'%foo%'%bar\")", "'%foo%'%bar")
+            tester('ntpath.expandvars("bar\'%foo%")', "bar'%foo%")
 
-    @unittest.skipUnless(support.FS_NONASCII, 'need support.FS_NONASCII')
+    @unittest.skipUnless(support.FS_NONASCII, "need support.FS_NONASCII")
     def test_expandvars_nonascii(self):
         def check(value, expected):
-            tester('ntpath.expandvars(%r)' % value, expected)
-        with support.EnvironmentVarGuard() as env:
+            tester("ntpath.expandvars(%r)" % value, expected)
+
+        with EnvironmentVarGuard() as env:
             env.clear()
             nonascii = support.FS_NONASCII
-            env['spam'] = nonascii
-            env[nonascii] = 'ham' + nonascii
-            check('$spam bar', '%s bar' % nonascii)
-            check('$%s bar' % nonascii, '$%s bar' % nonascii)
-            check('${spam}bar', '%sbar' % nonascii)
-            check('${%s}bar' % nonascii, 'ham%sbar' % nonascii)
-            check('$spam}bar', '%s}bar' % nonascii)
-            check('$%s}bar' % nonascii, '$%s}bar' % nonascii)
-            check('%spam% bar', '%s bar' % nonascii)
-            check('%{}% bar'.format(nonascii), 'ham%s bar' % nonascii)
-            check('%spam%bar', '%sbar' % nonascii)
-            check('%{}%bar'.format(nonascii), 'ham%sbar' % nonascii)
+            env["spam"] = nonascii
+            env[nonascii] = "ham" + nonascii
+            check("$spam bar", "%s bar" % nonascii)
+            check("$%s bar" % nonascii, "$%s bar" % nonascii)
+            check("${spam}bar", "%sbar" % nonascii)
+            check("${%s}bar" % nonascii, "ham%sbar" % nonascii)
+            check("$spam}bar", "%s}bar" % nonascii)
+            check("$%s}bar" % nonascii, "$%s}bar" % nonascii)
+            check("%spam% bar", "%s bar" % nonascii)
+            check("%{}% bar".format(nonascii), "ham%s bar" % nonascii)
+            check("%spam%bar", "%sbar" % nonascii)
+            check("%{}%bar".format(nonascii), "ham%sbar" % nonascii)
+
+    @support.requires_resource("cpu")
+    def test_expandvars_large(self):
+        expandvars = ntpath.expandvars
+        with EnvironmentVarGuard() as env:
+            env.clear()
+            env["A"] = "B"
+            n = 100000
+            self.assertEqual(expandvars("%A%" * n), "B" * n)
+            self.assertEqual(expandvars("%A%A" * n), "BA" * n)
+            self.assertEqual(expandvars("''" * n + "%%"), "''" * n + "%")
+            self.assertEqual(expandvars("%%" * n), "%" * n)
+            self.assertEqual(expandvars("$$" * n), "$" * n)
 
     def test_expanduser(self):
-        tester('ntpath.expanduser("test")', 'test')
+        tester('ntpath.expanduser("test")', "test")
 
-        with support.EnvironmentVarGuard() as env:
+        with EnvironmentVarGuard() as env:
             env.clear()
-            tester('ntpath.expanduser("~test")', '~test')
+            tester('ntpath.expanduser("~test")', "~test")
 
-            env['HOMEPATH'] = 'eric\\idle'
-            env['HOMEDRIVE'] = 'C:\\'
-            tester('ntpath.expanduser("~test")', 'C:\\eric\\test')
-            tester('ntpath.expanduser("~")', 'C:\\eric\\idle')
-
-            del env['HOMEDRIVE']
-            tester('ntpath.expanduser("~test")', 'eric\\test')
-            tester('ntpath.expanduser("~")', 'eric\\idle')
+            env["HOMEPATH"] = "eric\\idle"
+            env["HOMEDRIVE"] = "C:\\"
+            tester('ntpath.expanduser("~test")', "C:\\eric\\test")
+            tester('ntpath.expanduser("~")', "C:\\eric\\idle")
+
+            del env["HOMEDRIVE"]
+            tester('ntpath.expanduser("~test")', "eric\\test")
+            tester('ntpath.expanduser("~")', "eric\\idle")
 
             env.clear()
-            env['USERPROFILE'] = 'C:\\eric\\idle'
-            tester('ntpath.expanduser("~test")', 'C:\\eric\\test')
-            tester('ntpath.expanduser("~")', 'C:\\eric\\idle')
+            env["USERPROFILE"] = "C:\\eric\\idle"
+            tester('ntpath.expanduser("~test")', "C:\\eric\\test")
+            tester('ntpath.expanduser("~")', "C:\\eric\\idle")
 
             env.clear()
-            env['HOME'] = 'C:\\idle\\eric'
-            tester('ntpath.expanduser("~test")', 'C:\\idle\\test')
-            tester('ntpath.expanduser("~")', 'C:\\idle\\eric')
-
-            tester('ntpath.expanduser("~test\\foo\\bar")',
-                   'C:\\idle\\test\\foo\\bar')
-            tester('ntpath.expanduser("~test/foo/bar")',
-                   'C:\\idle\\test/foo/bar')
-            tester('ntpath.expanduser("~\\foo\\bar")',
-                   'C:\\idle\\eric\\foo\\bar')
-            tester('ntpath.expanduser("~/foo/bar")',
-                   'C:\\idle\\eric/foo/bar')
+            env["HOME"] = "C:\\idle\\eric"
+            tester('ntpath.expanduser("~test")', "C:\\idle\\test")
+            tester('ntpath.expanduser("~")', "C:\\idle\\eric")
+
+            tester('ntpath.expanduser("~test\\foo\\bar")', "C:\\idle\\test\\foo\\bar")
+            tester('ntpath.expanduser("~test/foo/bar")', "C:\\idle\\test/foo/bar")
+            tester('ntpath.expanduser("~\\foo\\bar")', "C:\\idle\\eric\\foo\\bar")
+            tester('ntpath.expanduser("~/foo/bar")', "C:\\idle\\eric/foo/bar")
 
     def test_abspath(self):
         # ntpath.abspath() can only be used on a system with the "nt" module
@@ -304,108 +377,112 @@
         # from any platform.
         try:
             import nt
+
             tester('ntpath.abspath("C:\\")', "C:\\")
-            with support.temp_cwd(support.TESTFN) as cwd_dir: # bpo-31047
+            with support.temp_cwd(support.TESTFN) as cwd_dir:  # bpo-31047
                 tester('ntpath.abspath("")', cwd_dir)
                 tester('ntpath.abspath(" ")', cwd_dir + "\\ ")
                 tester('ntpath.abspath("?")', cwd_dir + "\\?")
                 drive, _ = ntpath.splitdrive(cwd_dir)
                 tester('ntpath.abspath("/abc/")', drive + "\\abc")
         except ImportError:
-            self.skipTest('nt module not available')
+            self.skipTest("nt module not available")
 
     def test_relpath(self):
-        tester('ntpath.relpath("a")', 'a')
-        tester('ntpath.relpath(os.path.abspath("a"))', 'a')
-        tester('ntpath.relpath("a/b")', 'a\\b')
-        tester('ntpath.relpath("../a/b")', '..\\a\\b')
+        tester('ntpath.relpath("a")', "a")
+        tester('ntpath.relpath(os.path.abspath("a"))', "a")
+        tester('ntpath.relpath("a/b")', "a\\b")
+        tester('ntpath.relpath("../a/b")', "..\\a\\b")
         with support.temp_cwd(support.TESTFN) as cwd_dir:
             currentdir = os.path.basename(cwd_dir)
-            tester('ntpath.relpath("a", "../b")', '..\\'+currentdir+'\\a')
-            tester('ntpath.relpath("a/b", "../c")', '..\\'+currentdir+'\\a\\b')
-        tester('ntpath.relpath("a", "b/c")', '..\\..\\a')
-        tester('ntpath.relpath("c:/foo/bar/bat", "c:/x/y")', '..\\..\\foo\\bar\\bat')
-        tester('ntpath.relpath("//conky/mountpoint/a", "//conky/mountpoint/b/c")', '..\\..\\a')
-        tester('ntpath.relpath("a", "a")', '.')
-        tester('ntpath.relpath("/foo/bar/bat", "/x/y/z")', '..\\..\\..\\foo\\bar\\bat')
-        tester('ntpath.relpath("/foo/bar/bat", "/foo/bar")', 'bat')
-        tester('ntpath.relpath("/foo/bar/bat", "/")', 'foo\\bar\\bat')
-        tester('ntpath.relpath("/", "/foo/bar/bat")', '..\\..\\..')
-        tester('ntpath.relpath("/foo/bar/bat", "/x")', '..\\foo\\bar\\bat')
-        tester('ntpath.relpath("/x", "/foo/bar/bat")', '..\\..\\..\\x')
-        tester('ntpath.relpath("/", "/")', '.')
-        tester('ntpath.relpath("/a", "/a")', '.')
-        tester('ntpath.relpath("/a/b", "/a/b")', '.')
-        tester('ntpath.relpath("c:/foo", "C:/FOO")', '.')
+            tester('ntpath.relpath("a", "../b")', "..\\" + currentdir + "\\a")
+            tester('ntpath.relpath("a/b", "../c")', "..\\" + currentdir + "\\a\\b")
+        tester('ntpath.relpath("a", "b/c")', "..\\..\\a")
+        tester('ntpath.relpath("c:/foo/bar/bat", "c:/x/y")', "..\\..\\foo\\bar\\bat")
+        tester(
+            'ntpath.relpath("//conky/mountpoint/a", "//conky/mountpoint/b/c")',
+            "..\\..\\a",
+        )
+        tester('ntpath.relpath("a", "a")', ".")
+        tester('ntpath.relpath("/foo/bar/bat", "/x/y/z")', "..\\..\\..\\foo\\bar\\bat")
+        tester('ntpath.relpath("/foo/bar/bat", "/foo/bar")', "bat")
+        tester('ntpath.relpath("/foo/bar/bat", "/")', "foo\\bar\\bat")
+        tester('ntpath.relpath("/", "/foo/bar/bat")', "..\\..\\..")
+        tester('ntpath.relpath("/foo/bar/bat", "/x")', "..\\foo\\bar\\bat")
+        tester('ntpath.relpath("/x", "/foo/bar/bat")', "..\\..\\..\\x")
+        tester('ntpath.relpath("/", "/")', ".")
+        tester('ntpath.relpath("/a", "/a")', ".")
+        tester('ntpath.relpath("/a/b", "/a/b")', ".")
+        tester('ntpath.relpath("c:/foo", "C:/FOO")', ".")
 
     def test_commonpath(self):
         def check(paths, expected):
-            tester(('ntpath.commonpath(%r)' % paths).replace('\\\\', '\\'),
-                   expected)
+            tester(("ntpath.commonpath(%r)" % paths).replace("\\\\", "\\"), expected)
+
         def check_error(exc, paths):
             self.assertRaises(exc, ntpath.commonpath, paths)
-            self.assertRaises(exc, ntpath.commonpath,
-                              [os.fsencode(p) for p in paths])
+            self.assertRaises(exc, ntpath.commonpath, [os.fsencode(p) for p in paths])
 
         self.assertRaises(ValueError, ntpath.commonpath, [])
-        check_error(ValueError, ['C:\\Program Files', 'Program Files'])
-        check_error(ValueError, ['C:\\Program Files', 'C:Program Files'])
-        check_error(ValueError, ['\\Program Files', 'Program Files'])
-        check_error(ValueError, ['Program Files', 'C:\\Program Files'])
-        check(['C:\\Program Files'], 'C:\\Program Files')
-        check(['C:\\Program Files', 'C:\\Program Files'], 'C:\\Program Files')
-        check(['C:\\Program Files\\', 'C:\\Program Files'],
-              'C:\\Program Files')
-        check(['C:\\Program Files\\', 'C:\\Program Files\\'],
-              'C:\\Program Files')
-        check(['C:\\\\Program Files', 'C:\\Program Files\\\\'],
-              'C:\\Program Files')
-        check(['C:\\.\\Program Files', 'C:\\Program Files\\.'],
-              'C:\\Program Files')
-        check(['C:\\', 'C:\\bin'], 'C:\\')
-        check(['C:\\Program Files', 'C:\\bin'], 'C:\\')
-        check(['C:\\Program Files', 'C:\\Program Files\\Bar'],
-              'C:\\Program Files')
-        check(['C:\\Program Files\\Foo', 'C:\\Program Files\\Bar'],
-              'C:\\Program Files')
-        check(['C:\\Program Files', 'C:\\Projects'], 'C:\\')
-        check(['C:\\Program Files\\', 'C:\\Projects'], 'C:\\')
-
-        check(['C:\\Program Files\\Foo', 'C:/Program Files/Bar'],
-              'C:\\Program Files')
-        check(['C:\\Program Files\\Foo', 'c:/program files/bar'],
-              'C:\\Program Files')
-        check(['c:/program files/bar', 'C:\\Program Files\\Foo'],
-              'c:\\program files')
-
-        check_error(ValueError, ['C:\\Program Files', 'D:\\Program Files'])
-
-        check(['spam'], 'spam')
-        check(['spam', 'spam'], 'spam')
-        check(['spam', 'alot'], '')
-        check(['and\\jam', 'and\\spam'], 'and')
-        check(['and\\\\jam', 'and\\spam\\\\'], 'and')
-        check(['and\\.\\jam', '.\\and\\spam'], 'and')
-        check(['and\\jam', 'and\\spam', 'alot'], '')
-        check(['and\\jam', 'and\\spam', 'and'], 'and')
-        check(['C:and\\jam', 'C:and\\spam'], 'C:and')
-
-        check([''], '')
-        check(['', 'spam\\alot'], '')
-        check_error(ValueError, ['', '\\spam\\alot'])
-
-        self.assertRaises(TypeError, ntpath.commonpath,
-                          [b'C:\\Program Files', 'C:\\Program Files\\Foo'])
-        self.assertRaises(TypeError, ntpath.commonpath,
-                          [b'C:\\Program Files', 'Program Files\\Foo'])
-        self.assertRaises(TypeError, ntpath.commonpath,
-                          [b'Program Files', 'C:\\Program Files\\Foo'])
-        self.assertRaises(TypeError, ntpath.commonpath,
-                          ['C:\\Program Files', b'C:\\Program Files\\Foo'])
-        self.assertRaises(TypeError, ntpath.commonpath,
-                          ['C:\\Program Files', b'Program Files\\Foo'])
-        self.assertRaises(TypeError, ntpath.commonpath,
-                          ['Program Files', b'C:\\Program Files\\Foo'])
+        check_error(ValueError, ["C:\\Program Files", "Program Files"])
+        check_error(ValueError, ["C:\\Program Files", "C:Program Files"])
+        check_error(ValueError, ["\\Program Files", "Program Files"])
+        check_error(ValueError, ["Program Files", "C:\\Program Files"])
+        check(["C:\\Program Files"], "C:\\Program Files")
+        check(["C:\\Program Files", "C:\\Program Files"], "C:\\Program Files")
+        check(["C:\\Program Files\\", "C:\\Program Files"], "C:\\Program Files")
+        check(["C:\\Program Files\\", "C:\\Program Files\\"], "C:\\Program Files")
+        check(["C:\\\\Program Files", "C:\\Program Files\\\\"], "C:\\Program Files")
+        check(["C:\\.\\Program Files", "C:\\Program Files\\."], "C:\\Program Files")
+        check(["C:\\", "C:\\bin"], "C:\\")
+        check(["C:\\Program Files", "C:\\bin"], "C:\\")
+        check(["C:\\Program Files", "C:\\Program Files\\Bar"], "C:\\Program Files")
+        check(["C:\\Program Files\\Foo", "C:\\Program Files\\Bar"], "C:\\Program Files")
+        check(["C:\\Program Files", "C:\\Projects"], "C:\\")
+        check(["C:\\Program Files\\", "C:\\Projects"], "C:\\")
+
+        check(["C:\\Program Files\\Foo", "C:/Program Files/Bar"], "C:\\Program Files")
+        check(["C:\\Program Files\\Foo", "c:/program files/bar"], "C:\\Program Files")
+        check(["c:/program files/bar", "C:\\Program Files\\Foo"], "c:\\program files")
+
+        check_error(ValueError, ["C:\\Program Files", "D:\\Program Files"])
+
+        check(["spam"], "spam")
+        check(["spam", "spam"], "spam")
+        check(["spam", "alot"], "")
+        check(["and\\jam", "and\\spam"], "and")
+        check(["and\\\\jam", "and\\spam\\\\"], "and")
+        check(["and\\.\\jam", ".\\and\\spam"], "and")
+        check(["and\\jam", "and\\spam", "alot"], "")
+        check(["and\\jam", "and\\spam", "and"], "and")
+        check(["C:and\\jam", "C:and\\spam"], "C:and")
+
+        check([""], "")
+        check(["", "spam\\alot"], "")
+        check_error(ValueError, ["", "\\spam\\alot"])
+
+        self.assertRaises(
+            TypeError,
+            ntpath.commonpath,
+            [b"C:\\Program Files", "C:\\Program Files\\Foo"],
+        )
+        self.assertRaises(
+            TypeError, ntpath.commonpath, [b"C:\\Program Files", "Program Files\\Foo"]
+        )
+        self.assertRaises(
+            TypeError, ntpath.commonpath, [b"Program Files", "C:\\Program Files\\Foo"]
+        )
+        self.assertRaises(
+            TypeError,
+            ntpath.commonpath,
+            ["C:\\Program Files", b"C:\\Program Files\\Foo"],
+        )
+        self.assertRaises(
+            TypeError, ntpath.commonpath, ["C:\\Program Files", b"Program Files\\Foo"]
+        )
+        self.assertRaises(
+            TypeError, ntpath.commonpath, ["Program Files", b"C:\\Program Files\\Foo"]
+        )
 
     def test_sameopenfile(self):
         with TemporaryFile() as tf1, TemporaryFile() as tf2:
@@ -455,9 +532,10 @@
             self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$"))
             self.assertTrue(ntpath.ismount(b"\\\\localhost\\c$\\"))
 
+
 class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase):
     pathmodule = ntpath
-    attributes = ['relpath', 'splitunc']
+    attributes = ["relpath", "splitunc"]
 
 
 class PathLikeTests(unittest.TestCase):
@@ -468,7 +546,7 @@
         self.file_name = support.TESTFN.lower()
         self.file_path = FakePath(support.TESTFN)
         self.addCleanup(support.unlink, self.file_name)
-        with open(self.file_name, 'xb', 0) as file:
+        with open(self.file_name, "xb", 0) as file:
             file.write(b"test_ntpath.PathLikeTests")
 
     def assertPathEqual(self, func):
@@ -481,8 +559,9 @@
         self.assertPathEqual(self.path.isabs)
 
     def test_path_join(self):
-        self.assertEqual(self.path.join('a', FakePath('b'), 'c'),
-                         self.path.join('a', 'b', 'c'))
+        self.assertEqual(
+            self.path.join("a", FakePath("b"), "c"), self.path.join("a", "b", "c")
+        )
 
     def test_path_split(self):
         self.assertPathEqual(self.path.split)
Index: Python-3.6.15/Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ Python-3.6.15/Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst	2025-12-15 22:47:15.854142448 +0100
@@ -0,0 +1 @@
+Fix quadratic complexity in :func:`os.path.expandvars`.
openSUSE Build Service is sponsored by