diff options
-rw-r--r-- | changelog.md | 3 | ||||
-rw-r--r-- | lib/pure/os.nim | 49 | ||||
-rw-r--r-- | lib/pure/pathnorm.nim | 21 | ||||
-rw-r--r-- | tests/stdlib/tos.nim | 81 |
4 files changed, 68 insertions, 86 deletions
diff --git a/changelog.md b/changelog.md index e48f72023..7fad8f822 100644 --- a/changelog.md +++ b/changelog.md @@ -111,6 +111,9 @@ proc enumToString*(enums: openArray[enum]): string = (default value: true) that can be set to `false` for better Posix interoperability. (Bug #9619.) +- `os.joinPath` and `os.normalizePath` handle edge cases like ``"a/b/../../.."`` + differently. + ### Language additions diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 6521d827c..1ad276b0a 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -984,36 +984,37 @@ proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScr ## ## Warning: URL-encoded and Unicode attempts at directory traversal are not detected. ## Triple dot is not handled. - let isAbs = isAbsolute(path) - var stack: seq[string] = @[] - for p in split(path, {DirSep}): - case p - of "", ".": - continue - of "..": - if stack.len == 0: - if isAbs: - discard # collapse all double dots on absoluta paths - else: + path = pathnorm.normalizePath(path) + when false: + let isAbs = isAbsolute(path) + var stack: seq[string] = @[] + for p in split(path, {DirSep}): + case p + of "", ".": + continue + of "..": + if stack.len == 0: + if isAbs: + discard # collapse all double dots on absoluta paths + else: + stack.add(p) + elif stack[^1] == "..": stack.add(p) - elif stack[^1] == "..": - stack.add(p) + else: + discard stack.pop() else: - discard stack.pop() - else: - stack.add(p) + stack.add(p) - if isAbs: - path = DirSep & join(stack, $DirSep) - elif stack.len > 0: - path = join(stack, $DirSep) - else: - path = "." + if isAbs: + path = DirSep & join(stack, $DirSep) + elif stack.len > 0: + path = join(stack, $DirSep) + else: + path = "." proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Returns a normalized path for the current OS. See `<#normalizePath>`_ - result = path - normalizePath(result) + result = pathnorm.normalizePath(path) when defined(Windows) and not defined(nimscript): proc openHandle(path: string, followSymlink=true, writeAccess=false): Handle = diff --git a/lib/pure/pathnorm.nim b/lib/pure/pathnorm.nim index a33afefbd..4a7d74bf8 100644 --- a/lib/pure/pathnorm.nim +++ b/lib/pure/pathnorm.nim @@ -66,19 +66,26 @@ proc addNormalizePath*(x: string; result: var string; state: var int; dirSep = D if (state shr 1 == 0) and isSlash(x, b): result.add dirSep state = state or 1 - elif result.len > (state and 1) and isDotDot(x, b): - var d = result.len - # f/.. - while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}: - dec d - if d > 0: setLen(result, d-1) + elif isDotDot(x, b): + if (state shr 1) >= 1: + var d = result.len + # f/.. + while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}: + dec d + if d > 0: + setLen(result, d-1) + dec state, 2 + else: + if result.len > 0 and result[^1] notin {DirSep, AltSep}: + result.add dirSep + result.add substr(x, b[0], b[1]) elif isDot(x, b): discard "discard the dot" elif b[1] >= b[0]: if result.len > 0 and result[^1] notin {DirSep, AltSep}: result.add dirSep result.add substr(x, b[0], b[1]) - inc state, 2 + inc state, 2 proc normalizePath*(path: string; dirSep = DirSep): string = ## Example: diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim index 467f64fff..66ca3de33 100644 --- a/tests/stdlib/tos.nim +++ b/tests/stdlib/tos.nim @@ -190,61 +190,32 @@ block walkDirRec: removeDir("walkdir_test") block normalizedPath: - when defined(posix): - block relative: - doAssert normalizedPath(".") == "." - doAssert normalizedPath("..") == ".." - doAssert normalizedPath("../") == ".." - doAssert normalizedPath("../..") == "../.." - doAssert normalizedPath("../a/..") == ".." - doAssert normalizedPath("../a/../") == ".." - doAssert normalizedPath("./") == "." - - block absolute: - doAssert normalizedPath("/") == "/" - doAssert normalizedPath("/.") == "/" - doAssert normalizedPath("/..") == "/" - doAssert normalizedPath("/../") == "/" - doAssert normalizedPath("/../..") == "/" - doAssert normalizedPath("/../../") == "/" - doAssert normalizedPath("/../../../") == "/" - doAssert normalizedPath("/a/b/../../foo") == "/foo" - doAssert normalizedPath("/a/b/../../../foo") == "/foo" - doAssert normalizedPath("/./") == "/" - doAssert normalizedPath("//") == "/" - doAssert normalizedPath("///") == "/" - doAssert normalizedPath("/a//b") == "/a/b" - doAssert normalizedPath("/a///b") == "/a/b" - doAssert normalizedPath("/a/b/c/..") == "/a/b" - doAssert normalizedPath("/a/b/c/../") == "/a/b" - - else: - block relative: - doAssert normalizedPath(".") == "." - doAssert normalizedPath("..") == ".." - doAssert normalizedPath("..\\") == ".." - doAssert normalizedPath("..\\..") == "..\\.." - doAssert normalizedPath("..\\a\\..") == ".." - doAssert normalizedPath("..\\a\\..\\") == ".." - doAssert normalizedPath(".\\") == "." - - block absolute: - doAssert normalizedPath("\\") == "\\" - doAssert normalizedPath("\\.") == "\\" - doAssert normalizedPath("\\..") == "\\" - doAssert normalizedPath("\\..\\") == "\\" - doAssert normalizedPath("\\..\\..") == "\\" - doAssert normalizedPath("\\..\\..\\") == "\\" - doAssert normalizedPath("\\..\\..\\..\\") == "\\" - doAssert normalizedPath("\\a\\b\\..\\..\\foo") == "\\foo" - doAssert normalizedPath("\\a\\b\\..\\..\\..\\foo") == "\\foo" - doAssert normalizedPath("\\.\\") == "\\" - doAssert normalizedPath("\\\\") == "\\" - doAssert normalizedPath("\\\\\\") == "\\" - doAssert normalizedPath("\\a\\\\b") == "\\a\\b" - doAssert normalizedPath("\\a\\\\\\b") == "\\a\\b" - doAssert normalizedPath("\\a\\b\\c\\..") == "\\a\\b" - doAssert normalizedPath("\\a\\b\\c\\..\\") == "\\a\\b" + block relative: + doAssert normalizedPath(".") == "" + doAssert normalizedPath("..") == ".." + doAssert normalizedPath("../") == ".." + doAssert normalizedPath("../..") == unixToNativePath"../.." + doAssert normalizedPath("../a/..") == ".." + doAssert normalizedPath("../a/../") == ".." + doAssert normalizedPath("./") == "" + + block absolute: + doAssert normalizedPath("/") == unixToNativePath"/" + doAssert normalizedPath("/.") == unixToNativePath"/" + doAssert normalizedPath("/..") == unixToNativePath"/.." + doAssert normalizedPath("/../") == unixToNativePath"/.." + doAssert normalizedPath("/../..") == unixToNativePath"/../.." + doAssert normalizedPath("/../../") == unixToNativePath"/../.." + doAssert normalizedPath("/../../../") == unixToNativePath"/../../.." + doAssert normalizedPath("/a/b/../../foo") == unixToNativePath"/foo" + doAssert normalizedPath("/a/b/../../../foo") == unixToNativePath"/../foo" + doAssert normalizedPath("/./") == unixToNativePath"/" + doAssert normalizedPath("//") == unixToNativePath"/" + doAssert normalizedPath("///") == unixToNativePath"/" + doAssert normalizedPath("/a//b") == unixToNativePath"/a/b" + doAssert normalizedPath("/a///b") == unixToNativePath"/a/b" + doAssert normalizedPath("/a/b/c/..") == unixToNativePath"/a/b" + doAssert normalizedPath("/a/b/c/../") == unixToNativePath"/a/b" block isHidden: when defined(posix): |