summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorneonene <53406459+neonene@users.noreply.github.com>2022-01-13 23:35:42 (GMT)
committerGitHub <noreply@github.com>2022-01-13 23:35:42 (GMT)
commitd4e64cd4b0ea431d4e371f9b0a25f6b75a069dc1 (patch)
treececdf1d08cb790283d061b8263086354976c4939
parentb8ddf7e794e5316981016d6d014862e3c4ce149a (diff)
downloadcpython-d4e64cd4b0ea431d4e371f9b0a25f6b75a069dc1.zip
cpython-d4e64cd4b0ea431d4e371f9b0a25f6b75a069dc1.tar.gz
cpython-d4e64cd4b0ea431d4e371f9b0a25f6b75a069dc1.tar.bz2
bpo-46362: Ensure ntpath.abspath() uses the Windows API correctly (GH-30571)
This makes ntpath.abspath()/getpath_abspath() follow normpath(), since some WinAPIs such as PathCchSkipRoot() require backslashed paths.
-rw-r--r--Include/internal/pycore_fileutils.h3
-rw-r--r--Lib/ntpath.py2
-rw-r--r--Lib/test/test_embed.py27
-rw-r--r--Lib/test/test_ntpath.py34
-rw-r--r--Misc/NEWS.d/next/Windows/2022-01-13-22-31-09.bpo-46362.f2cuEb.rst2
-rw-r--r--Modules/getpath.c6
-rw-r--r--Modules/posixmodule.c45
-rw-r--r--Python/fileutils.c37
8 files changed, 114 insertions, 42 deletions
diff --git a/Include/internal/pycore_fileutils.h b/Include/internal/pycore_fileutils.h
index 61c11a8..3ce8108 100644
--- a/Include/internal/pycore_fileutils.h
+++ b/Include/internal/pycore_fileutils.h
@@ -235,6 +235,9 @@ extern int _Py_EncodeNonUnicodeWchar_InPlace(
extern int _Py_isabs(const wchar_t *path);
extern int _Py_abspath(const wchar_t *path, wchar_t **abspath_p);
+#ifdef MS_WINDOWS
+extern int _PyOS_getfullpathname(const wchar_t *path, wchar_t **abspath_p);
+#endif
extern wchar_t * _Py_join_relfile(const wchar_t *dirname,
const wchar_t *relfile);
extern int _Py_add_relfile(wchar_t *dirname,
diff --git a/Lib/ntpath.py b/Lib/ntpath.py
index 58483a0..041ebc7 100644
--- a/Lib/ntpath.py
+++ b/Lib/ntpath.py
@@ -551,7 +551,7 @@ else: # use native Windows method on Windows
def abspath(path):
"""Return the absolute version of a path."""
try:
- return normpath(_getfullpathname(path))
+ return _getfullpathname(normpath(path))
except (OSError, ValueError):
return _abspath_fallback(path)
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index dd43669..02bbe35 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -1404,6 +1404,33 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
api=API_COMPAT, env=env,
ignore_stderr=True, cwd=tmpdir)
+ @unittest.skipUnless(MS_WINDOWS, 'specific to Windows')
+ def test_getpath_abspath_win32(self):
+ # Check _Py_abspath() is passed a backslashed path not to fall back to
+ # GetFullPathNameW() on startup, which (re-)normalizes the path overly.
+ # Currently, _Py_normpath() doesn't trim trailing dots and spaces.
+ CASES = [
+ ("C:/a. . .", "C:\\a. . ."),
+ ("C:\\a. . .", "C:\\a. . ."),
+ ("\\\\?\\C:////a////b. . .", "\\\\?\\C:\\a\\b. . ."),
+ ("//a/b/c. . .", "\\\\a\\b\\c. . ."),
+ ("\\\\a\\b\\c. . .", "\\\\a\\b\\c. . ."),
+ ("a. . .", f"{os.getcwd()}\\a"), # relpath gets fully normalized
+ ]
+ out, err = self.run_embedded_interpreter(
+ "test_init_initialize_config",
+ env=dict(PYTHONPATH=os.path.pathsep.join(c[0] for c in CASES))
+ )
+ self.assertEqual(err, "")
+ try:
+ out = json.loads(out)
+ except json.JSONDecodeError:
+ self.fail(f"fail to decode stdout: {out!r}")
+
+ results = out['config']["module_search_paths"]
+ for (_, expected), result in zip(CASES, results):
+ self.assertEqual(result, expected)
+
def test_global_pathconfig(self):
# Test C API functions getting the path configuration:
#
diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py
index cc29881..99a77e3 100644
--- a/Lib/test/test_ntpath.py
+++ b/Lib/test/test_ntpath.py
@@ -613,6 +613,40 @@ class TestNtpath(NtpathTestCase):
@unittest.skipUnless(nt, "abspath requires 'nt' module")
def test_abspath(self):
tester('ntpath.abspath("C:\\")', "C:\\")
+ tester('ntpath.abspath("\\\\?\\C:////spam////eggs. . .")', "\\\\?\\C:\\spam\\eggs")
+ tester('ntpath.abspath("\\\\.\\C:////spam////eggs. . .")', "\\\\.\\C:\\spam\\eggs")
+ tester('ntpath.abspath("//spam//eggs. . .")', "\\\\spam\\eggs")
+ tester('ntpath.abspath("\\\\spam\\\\eggs. . .")', "\\\\spam\\eggs")
+ tester('ntpath.abspath("C:/spam. . .")', "C:\\spam")
+ tester('ntpath.abspath("C:\\spam. . .")', "C:\\spam")
+ tester('ntpath.abspath("C:/nul")', "\\\\.\\nul")
+ tester('ntpath.abspath("C:\\nul")', "\\\\.\\nul")
+ tester('ntpath.abspath("//..")', "\\\\")
+ tester('ntpath.abspath("//../")', "\\\\..\\")
+ tester('ntpath.abspath("//../..")', "\\\\..\\")
+ tester('ntpath.abspath("//../../")', "\\\\..\\..\\")
+ tester('ntpath.abspath("//../../../")', "\\\\..\\..\\")
+ tester('ntpath.abspath("//../../../..")', "\\\\..\\..\\")
+ tester('ntpath.abspath("//../../../../")', "\\\\..\\..\\")
+ tester('ntpath.abspath("//server")', "\\\\server")
+ tester('ntpath.abspath("//server/")', "\\\\server\\")
+ tester('ntpath.abspath("//server/..")', "\\\\server\\")
+ tester('ntpath.abspath("//server/../")', "\\\\server\\..\\")
+ tester('ntpath.abspath("//server/../..")', "\\\\server\\..\\")
+ tester('ntpath.abspath("//server/../../")', "\\\\server\\..\\")
+ tester('ntpath.abspath("//server/../../..")', "\\\\server\\..\\")
+ tester('ntpath.abspath("//server/../../../")', "\\\\server\\..\\")
+ tester('ntpath.abspath("//server/share")', "\\\\server\\share")
+ tester('ntpath.abspath("//server/share/")', "\\\\server\\share\\")
+ tester('ntpath.abspath("//server/share/..")', "\\\\server\\share\\")
+ tester('ntpath.abspath("//server/share/../")', "\\\\server\\share\\")
+ tester('ntpath.abspath("//server/share/../..")', "\\\\server\\share\\")
+ tester('ntpath.abspath("//server/share/../../")', "\\\\server\\share\\")
+ tester('ntpath.abspath("C:\\nul. . .")', "\\\\.\\nul")
+ tester('ntpath.abspath("//... . .")', "\\\\")
+ tester('ntpath.abspath("//.. . . .")', "\\\\")
+ tester('ntpath.abspath("//../... . .")', "\\\\..\\")
+ tester('ntpath.abspath("//../.. . . .")', "\\\\..\\")
with os_helper.temp_cwd(os_helper.TESTFN) as cwd_dir: # bpo-31047
tester('ntpath.abspath("")', cwd_dir)
tester('ntpath.abspath(" ")', cwd_dir + "\\ ")
diff --git a/Misc/NEWS.d/next/Windows/2022-01-13-22-31-09.bpo-46362.f2cuEb.rst b/Misc/NEWS.d/next/Windows/2022-01-13-22-31-09.bpo-46362.f2cuEb.rst
new file mode 100644
index 0000000..0b59cd2
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2022-01-13-22-31-09.bpo-46362.f2cuEb.rst
@@ -0,0 +1,2 @@
+os.path.abspath("C:\CON") is now fixed to return "\\.\CON", not the same path.
+The regression was true of all legacy DOS devices such as COM1, LPT1, or NUL. \ No newline at end of file
diff --git a/Modules/getpath.c b/Modules/getpath.c
index fdfe929..5c646c9 100644
--- a/Modules/getpath.c
+++ b/Modules/getpath.c
@@ -59,7 +59,7 @@ getpath_abspath(PyObject *Py_UNUSED(self), PyObject *args)
{
PyObject *r = NULL;
PyObject *pathobj;
- const wchar_t *path;
+ wchar_t *path;
if (!PyArg_ParseTuple(args, "U", &pathobj)) {
return NULL;
}
@@ -67,8 +67,8 @@ getpath_abspath(PyObject *Py_UNUSED(self), PyObject *args)
path = PyUnicode_AsWideCharString(pathobj, &len);
if (path) {
wchar_t *abs;
- if (_Py_abspath(path, &abs) == 0 && abs) {
- r = PyUnicode_FromWideChar(_Py_normpath(abs, -1), -1);
+ if (_Py_abspath((const wchar_t *)_Py_normpath(path, -1), &abs) == 0 && abs) {
+ r = PyUnicode_FromWideChar(abs, -1);
PyMem_RawFree((void *)abs);
} else {
PyErr_SetString(PyExc_OSError, "failed to make path absolute");
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 904f8bf..7b5c3ef 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -4240,6 +4240,48 @@ os_listdir_impl(PyObject *module, path_t *path)
}
#ifdef MS_WINDOWS
+int
+_PyOS_getfullpathname(const wchar_t *path, wchar_t **abspath_p)
+{
+ wchar_t woutbuf[MAX_PATH], *woutbufp = woutbuf;
+ DWORD result;
+
+ result = GetFullPathNameW(path,
+ Py_ARRAY_LENGTH(woutbuf), woutbuf,
+ NULL);
+ if (!result) {
+ return -1;
+ }
+
+ if (result >= Py_ARRAY_LENGTH(woutbuf)) {
+ if ((size_t)result <= (size_t)PY_SSIZE_T_MAX / sizeof(wchar_t)) {
+ woutbufp = PyMem_RawMalloc((size_t)result * sizeof(wchar_t));
+ }
+ else {
+ woutbufp = NULL;
+ }
+ if (!woutbufp) {
+ *abspath_p = NULL;
+ return 0;
+ }
+
+ result = GetFullPathNameW(path, result, woutbufp, NULL);
+ if (!result) {
+ PyMem_RawFree(woutbufp);
+ return -1;
+ }
+ }
+
+ if (woutbufp != woutbuf) {
+ *abspath_p = woutbufp;
+ return 0;
+ }
+
+ *abspath_p = _PyMem_RawWcsdup(woutbufp);
+ return 0;
+}
+
+
/* A helper function for abspath on win32 */
/*[clinic input]
os._getfullpathname
@@ -4255,8 +4297,7 @@ os__getfullpathname_impl(PyObject *module, path_t *path)
{
wchar_t *abspath;
- /* _Py_abspath() is implemented with GetFullPathNameW() on Windows */
- if (_Py_abspath(path->wide, &abspath) < 0) {
+ if (_PyOS_getfullpathname(path->wide, &abspath) < 0) {
return win32_error_object("GetFullPathNameW", path->object);
}
if (abspath == NULL) {
diff --git a/Python/fileutils.c b/Python/fileutils.c
index 151c6fe..9a71b83 100644
--- a/Python/fileutils.c
+++ b/Python/fileutils.c
@@ -2049,42 +2049,7 @@ _Py_abspath(const wchar_t *path, wchar_t **abspath_p)
}
#ifdef MS_WINDOWS
- wchar_t woutbuf[MAX_PATH], *woutbufp = woutbuf;
- DWORD result;
-
- result = GetFullPathNameW(path,
- Py_ARRAY_LENGTH(woutbuf), woutbuf,
- NULL);
- if (!result) {
- return -1;
- }
-
- if (result >= Py_ARRAY_LENGTH(woutbuf)) {
- if ((size_t)result <= (size_t)PY_SSIZE_T_MAX / sizeof(wchar_t)) {
- woutbufp = PyMem_RawMalloc((size_t)result * sizeof(wchar_t));
- }
- else {
- woutbufp = NULL;
- }
- if (!woutbufp) {
- *abspath_p = NULL;
- return 0;
- }
-
- result = GetFullPathNameW(path, result, woutbufp, NULL);
- if (!result) {
- PyMem_RawFree(woutbufp);
- return -1;
- }
- }
-
- if (woutbufp != woutbuf) {
- *abspath_p = woutbufp;
- return 0;
- }
-
- *abspath_p = _PyMem_RawWcsdup(woutbufp);
- return 0;
+ return _PyOS_getfullpathname(path, abspath_p);
#else
wchar_t cwd[MAXPATHLEN + 1];
cwd[Py_ARRAY_LENGTH(cwd) - 1] = 0;