Skip to content

Commit 927b5af

Browse files
zoobaaisk
andauthored
bpo-42658: Use LCMapStringEx in ntpath.normcase to match OS behaviour for case-folding (GH-93591)
* bpo-42658: Use LCMapStringEx in ntpath.normcase to match OS behaviour for case-folding (GH-32010) * Use AsWideCharString to avoid memory leaks in deprectated unicode converter Co-authored-by: AN Long <aisk@users.noreply.github.com>
1 parent f384a8e commit 927b5af

File tree

5 files changed

+151
-9
lines changed

5 files changed

+151
-9
lines changed

Lib/ntpath.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import genericpath
2424
from genericpath import *
2525

26+
2627
__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
2728
"basename","dirname","commonprefix","getsize","getmtime",
2829
"getatime","getctime", "islink","exists","lexists","isdir","isfile",
@@ -41,14 +42,39 @@ def _get_bothseps(path):
4142
# Other normalizations (such as optimizing '../' away) are not done
4243
# (this is done by normpath).
4344

44-
def normcase(s):
45-
"""Normalize case of pathname.
46-
47-
Makes all characters lowercase and all slashes into backslashes."""
48-
s = os.fspath(s)
49-
if isinstance(s, bytes):
50-
return s.replace(b'/', b'\\').lower()
51-
else:
45+
try:
46+
from _winapi import (
47+
LCMapStringEx as _LCMapStringEx,
48+
LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT,
49+
LCMAP_LOWERCASE as _LCMAP_LOWERCASE)
50+
51+
def normcase(s):
52+
"""Normalize case of pathname.
53+
54+
Makes all characters lowercase and all slashes into backslashes.
55+
"""
56+
s = os.fspath(s)
57+
if not s:
58+
return s
59+
if isinstance(s, bytes):
60+
encoding = sys.getfilesystemencoding()
61+
s = s.decode(encoding, 'surrogateescape').replace('/', '\\')
62+
s = _LCMapStringEx(_LOCALE_NAME_INVARIANT,
63+
_LCMAP_LOWERCASE, s)
64+
return s.encode(encoding, 'surrogateescape')
65+
else:
66+
return _LCMapStringEx(_LOCALE_NAME_INVARIANT,
67+
_LCMAP_LOWERCASE,
68+
s.replace('/', '\\'))
69+
except ImportError:
70+
def normcase(s):
71+
"""Normalize case of pathname.
72+
73+
Makes all characters lowercase and all slashes into backslashes.
74+
"""
75+
s = os.fspath(s)
76+
if isinstance(s, bytes):
77+
return os.fsencode(os.fsdecode(s).replace('/', '\\').lower())
5278
return s.replace('/', '\\').lower()
5379

5480

Lib/test/test_ntpath.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,8 @@ def _check_function(self, func):
852852

853853
def test_path_normcase(self):
854854
self._check_function(self.path.normcase)
855+
if sys.platform == 'win32':
856+
self.assertEqual(ntpath.normcase('\u03a9\u2126'), 'ωΩ')
855857

856858
def test_path_isabs(self):
857859
self._check_function(self.path.isabs)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Support native Windows case-insensitive path comparisons by using
2+
``LCMapStringEx`` instead of :func:`str.lower` in :func:`ntpath.normcase`.
3+
Add ``LCMapStringEx`` to the :mod:`_winapi` module.

Modules/_winapi.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,68 @@ _winapi_PeekNamedPipe_impl(PyObject *module, HANDLE handle, int size)
15121512
}
15131513
}
15141514

1515+
/*[clinic input]
1516+
_winapi.LCMapStringEx
1517+
1518+
locale: unicode
1519+
flags: DWORD
1520+
src: unicode
1521+
1522+
[clinic start generated code]*/
1523+
1524+
static PyObject *
1525+
_winapi_LCMapStringEx_impl(PyObject *module, PyObject *locale, DWORD flags,
1526+
PyObject *src)
1527+
/*[clinic end generated code: output=8ea4c9d85a4a1f23 input=2fa6ebc92591731b]*/
1528+
{
1529+
if (flags & (LCMAP_SORTHANDLE | LCMAP_HASH | LCMAP_BYTEREV |
1530+
LCMAP_SORTKEY)) {
1531+
return PyErr_Format(PyExc_ValueError, "unsupported flags");
1532+
}
1533+
1534+
wchar_t *locale_ = PyUnicode_AsWideCharString(locale, NULL);
1535+
if (!locale_) {
1536+
return NULL;
1537+
}
1538+
wchar_t *src_ = PyUnicode_AsWideCharString(src, NULL);
1539+
if (!src_) {
1540+
PyMem_Free(locale_);
1541+
return NULL;
1542+
}
1543+
1544+
int dest_size = LCMapStringEx(locale_, flags, src_, -1, NULL, 0,
1545+
NULL, NULL, 0);
1546+
if (dest_size == 0) {
1547+
PyMem_Free(locale_);
1548+
PyMem_Free(src_);
1549+
return PyErr_SetFromWindowsErr(0);
1550+
}
1551+
1552+
wchar_t* dest = PyMem_NEW(wchar_t, dest_size);
1553+
if (dest == NULL) {
1554+
PyMem_Free(locale_);
1555+
PyMem_Free(src_);
1556+
return PyErr_NoMemory();
1557+
}
1558+
1559+
int nmapped = LCMapStringEx(locale_, flags, src_, -1, dest, dest_size,
1560+
NULL, NULL, 0);
1561+
if (nmapped == 0) {
1562+
DWORD error = GetLastError();
1563+
PyMem_Free(locale_);
1564+
PyMem_Free(src_);
1565+
PyMem_DEL(dest);
1566+
return PyErr_SetFromWindowsErr(error);
1567+
}
1568+
1569+
PyObject *ret = PyUnicode_FromWideChar(dest, dest_size - 1);
1570+
PyMem_Free(locale_);
1571+
PyMem_Free(src_);
1572+
PyMem_DEL(dest);
1573+
1574+
return ret;
1575+
}
1576+
15151577
/*[clinic input]
15161578
_winapi.ReadFile
15171579
@@ -2023,6 +2085,7 @@ static PyMethodDef winapi_functions[] = {
20232085
_WINAPI_OPENFILEMAPPING_METHODDEF
20242086
_WINAPI_OPENPROCESS_METHODDEF
20252087
_WINAPI_PEEKNAMEDPIPE_METHODDEF
2088+
_WINAPI_LCMAPSTRINGEX_METHODDEF
20262089
_WINAPI_READFILE_METHODDEF
20272090
_WINAPI_SETNAMEDPIPEHANDLESTATE_METHODDEF
20282091
_WINAPI_TERMINATEPROCESS_METHODDEF
@@ -2160,6 +2223,22 @@ static int winapi_exec(PyObject *m)
21602223
WINAPI_CONSTANT(F_DWORD, FILE_TYPE_PIPE);
21612224
WINAPI_CONSTANT(F_DWORD, FILE_TYPE_REMOTE);
21622225

2226+
WINAPI_CONSTANT("u", LOCALE_NAME_INVARIANT);
2227+
WINAPI_CONSTANT(F_DWORD, LOCALE_NAME_MAX_LENGTH);
2228+
WINAPI_CONSTANT("u", LOCALE_NAME_SYSTEM_DEFAULT);
2229+
WINAPI_CONSTANT("u", LOCALE_NAME_USER_DEFAULT);
2230+
2231+
WINAPI_CONSTANT(F_DWORD, LCMAP_FULLWIDTH);
2232+
WINAPI_CONSTANT(F_DWORD, LCMAP_HALFWIDTH);
2233+
WINAPI_CONSTANT(F_DWORD, LCMAP_HIRAGANA);
2234+
WINAPI_CONSTANT(F_DWORD, LCMAP_KATAKANA);
2235+
WINAPI_CONSTANT(F_DWORD, LCMAP_LINGUISTIC_CASING);
2236+
WINAPI_CONSTANT(F_DWORD, LCMAP_LOWERCASE);
2237+
WINAPI_CONSTANT(F_DWORD, LCMAP_SIMPLIFIED_CHINESE);
2238+
WINAPI_CONSTANT(F_DWORD, LCMAP_TITLECASE);
2239+
WINAPI_CONSTANT(F_DWORD, LCMAP_TRADITIONAL_CHINESE);
2240+
WINAPI_CONSTANT(F_DWORD, LCMAP_UPPERCASE);
2241+
21632242
WINAPI_CONSTANT("i", NULL);
21642243

21652244
return 0;

Modules/clinic/_winapi.c.h

Lines changed: 33 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy