From 9c5fa9c97c5c5336e60e4ae7a2e6e3f67acedfc7 Mon Sep 17 00:00:00 2001 From: neonene <53406459+neonene@users.noreply.github.com> Date: Fri, 7 Jan 2022 04:13:10 +0900 Subject: bpo-46208: Fix normalization of relative paths in _Py_normpath()/os.path.normpath (GH-30362) --- Lib/test/test_ntpath.py | 9 ++++++++ Lib/test/test_posixpath.py | 17 +++++++++++++++ .../2022-01-04-01-53-35.bpo-46208.i00Vz5.rst | 1 + Python/fileutils.c | 25 ++++++++++++++-------- 4 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-01-04-01-53-35.bpo-46208.i00Vz5.rst diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index a8d87e5..cc29881 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -235,6 +235,15 @@ class TestNtpath(NtpathTestCase): tester("ntpath.normpath('\\\\.\\NUL')", r'\\.\NUL') tester("ntpath.normpath('\\\\?\\D:/XY\\Z')", r'\\?\D:/XY\Z') + tester("ntpath.normpath('handbook/../../Tests/image.png')", r'..\Tests\image.png') + tester("ntpath.normpath('handbook/../../../Tests/image.png')", r'..\..\Tests\image.png') + tester("ntpath.normpath('handbook///../a/.././../b/c')", r'..\b\c') + tester("ntpath.normpath('handbook/a/../..///../../b/c')", r'..\..\b\c') + + tester("ntpath.normpath('//server/share/..')" , '\\\\server\\share\\') + tester("ntpath.normpath('//server/share/../')" , '\\\\server\\share\\') + tester("ntpath.normpath('//server/share/../..')", '\\\\server\\share\\') + tester("ntpath.normpath('//server/share/../../')", '\\\\server\\share\\') def test_realpath_curdir(self): expected = ntpath.normpath(os.getcwd()) diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index e4d8384..5fc4205 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -329,13 +329,30 @@ class PosixPathTest(unittest.TestCase): ("/..", "/"), ("/../", "/"), ("/..//", "/"), + ("//.", "//"), ("//..", "//"), + ("//...", "//..."), + ("//../foo", "//foo"), + ("//../../foo", "//foo"), ("/../foo", "/foo"), ("/../../foo", "/foo"), ("/../foo/../", "/"), ("/../foo/../bar", "/bar"), ("/../../foo/../bar/./baz/boom/..", "/bar/baz"), ("/../../foo/../bar/./baz/boom/.", "/bar/baz/boom"), + ("foo/../bar/baz", "bar/baz"), + ("foo/../../bar/baz", "../bar/baz"), + ("foo/../../../bar/baz", "../../bar/baz"), + ("foo///../bar/.././../baz/boom", "../baz/boom"), + ("foo/bar/../..///../../baz/boom", "../../baz/boom"), + ("/foo/..", "/"), + ("/foo/../..", "/"), + ("//foo/..", "//"), + ("//foo/../..", "//"), + ("///foo/..", "/"), + ("///foo/../..", "/"), + ("////foo/..", "/"), + ("/////foo/..", "/"), ] def test_normpath(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-01-04-01-53-35.bpo-46208.i00Vz5.rst b/Misc/NEWS.d/next/Core and Builtins/2022-01-04-01-53-35.bpo-46208.i00Vz5.rst new file mode 100644 index 0000000..92025a0 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-01-04-01-53-35.bpo-46208.i00Vz5.rst @@ -0,0 +1 @@ +Fix the regression of os.path.normpath("A/../../B") not returning expected "../B" but "B". \ No newline at end of file diff --git a/Python/fileutils.c b/Python/fileutils.c index cae6b75..d570be5 100644 --- a/Python/fileutils.c +++ b/Python/fileutils.c @@ -2218,11 +2218,11 @@ _Py_normpath(wchar_t *path, Py_ssize_t size) if (!path[0] || size == 0) { return path; } - wchar_t lastC = L'\0'; - wchar_t *p1 = path; wchar_t *pEnd = size >= 0 ? &path[size] : NULL; - wchar_t *p2 = path; - wchar_t *minP2 = path; + wchar_t *p1 = path; // sequentially scanned address in the path + wchar_t *p2 = path; // destination of a scanned character to be ljusted + wchar_t *minP2 = path; // the beginning of the destination range + wchar_t lastC = L'\0'; // the last ljusted character, p2[-1] in most cases #define IS_END(x) (pEnd ? (x) == pEnd : !*(x)) #ifdef ALTSEP @@ -2264,14 +2264,18 @@ _Py_normpath(wchar_t *path, Py_ssize_t size) *p2++ = lastC = *p1; } } - minP2 = p2; + if (sepCount) { + minP2 = p2; // Invalid path + } else { + minP2 = p2 - 1; // Absolute path has SEP at minP2 + } } #else // Skip past two leading SEPs else if (IS_SEP(&p1[0]) && IS_SEP(&p1[1]) && !IS_SEP(&p1[2])) { *p2++ = *p1++; *p2++ = *p1++; - minP2 = p2; + minP2 = p2 - 1; // Absolute path has SEP at minP2 lastC = SEP; } #endif /* MS_WINDOWS */ @@ -2292,8 +2296,11 @@ _Py_normpath(wchar_t *path, Py_ssize_t size) wchar_t *p3 = p2; while (p3 != minP2 && *--p3 == SEP) { } while (p3 != minP2 && *(p3 - 1) != SEP) { --p3; } - if (p3[0] == L'.' && p3[1] == L'.' && IS_SEP(&p3[2])) { - // Previous segment is also ../, so append instead + if (p2 == minP2 + || (p3[0] == L'.' && p3[1] == L'.' && IS_SEP(&p3[2]))) + { + // Previous segment is also ../, so append instead. + // Relative path does not absorb ../ at minP2 as well. *p2++ = L'.'; *p2++ = L'.'; lastC = L'.'; @@ -2314,7 +2321,7 @@ _Py_normpath(wchar_t *path, Py_ssize_t size) } } else { *p2++ = lastC = c; - } + } } *p2 = L'\0'; if (p2 != minP2) { -- cgit v0.12