]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-46208: Fix normalization of relative paths in _Py_normpath()/os.path.normpath...
authorneonene <53406459+neonene@users.noreply.github.com>
Thu, 6 Jan 2022 19:13:10 +0000 (04:13 +0900)
committerGitHub <noreply@github.com>
Thu, 6 Jan 2022 19:13:10 +0000 (19:13 +0000)
Lib/test/test_ntpath.py
Lib/test/test_posixpath.py
Misc/NEWS.d/next/Core and Builtins/2022-01-04-01-53-35.bpo-46208.i00Vz5.rst [new file with mode: 0644]
Python/fileutils.c

index a8d87e53db9e9077465b7eff7e68c5b7eb5eedec..cc298810492243fb9a2a5c3125aaf7c7adb32122 100644 (file)
@@ -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())
index e4d8384ef0b4bc857fe6539101e33ead862c08af..5fc4205beb125fe8685d682df0076ea1816d8591 100644 (file)
@@ -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 (file)
index 0000000..92025a0
--- /dev/null
@@ -0,0 +1 @@
+Fix the regression of os.path.normpath("A/../../B") not returning expected "../B" but "B".
\ No newline at end of file
index cae6b75b6ae9b96db764a8a3625df6c1769300fa..d570be5577ae97e192a37fb0e36dada2570172cf 100644 (file)
@@ -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) {