summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorDominik Picheta <dominikpicheta@googlemail.com>2018-07-07 10:12:07 +0100
committerGitHub <noreply@github.com>2018-07-07 10:12:07 +0100
commit53ce58f05024743cd1dc347b2e0cd0147bdbdb1d (patch)
treef6ebc7ebb75a3c1ae3650b715ca2e2dc635485fc
parent65942449912097a3bf781152d0c1db3a52855e69 (diff)
parentd65429d857f927b6110217611feb94b18ee7f2a4 (diff)
downloadNim-53ce58f05024743cd1dc347b2e0cd0147bdbdb1d.tar.gz
Merge pull request #6587 from FedericoCeratto/normalizePath
Add normalizePath and tests
-rw-r--r--lib/pure/os.nim45
-rw-r--r--tests/stdlib/tos.nim62
2 files changed, 105 insertions, 2 deletions
diff --git a/lib/pure/os.nim b/lib/pure/os.nim
index 599745176..6cf5e1fb7 100644
--- a/lib/pure/os.nim
+++ b/lib/pure/os.nim
@@ -298,8 +298,8 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [].} =
 
 proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
   tags: [ReadDirEffect].} =
-  ## Returns the full (`absolute`:idx:) path of the file `filename`,
-  ## raises OSError in case of an error.
+  ## Returns the full (`absolute`:idx:) path of an existing file `filename`,
+  ## raises OSError in case of an error. Follows symlinks.
   when defined(windows):
     var bufsize = MAX_PATH.int32
     when useWinUnicode:
@@ -338,6 +338,47 @@ proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
       result = $r
       c_free(cast[pointer](r))
 
+proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} =
+  ## Normalize a path.
+  ##
+  ## Consecutive directory separators are collapsed, including an initial double slash.
+  ##
+  ## On relative paths, double dot (..) sequences are collapsed if possible.
+  ## On absolute paths they are always collapsed.
+  ##
+  ## 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:
+          stack.add(p)
+      elif stack[^1] == "..":
+        stack.add(p)
+      else:
+        discard stack.pop()
+    else:
+      stack.add(p)
+
+  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: [].} =
+  ## Returns a normalized path for the current OS. See `<#normalizePath>`_
+  result = path
+  normalizePath(result)
+
 when defined(Windows):
   proc openHandle(path: string, followSymlink=true, writeAccess=false): Handle =
     var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL
diff --git a/tests/stdlib/tos.nim b/tests/stdlib/tos.nim
index c7643b701..6ccc29bf6 100644
--- a/tests/stdlib/tos.nim
+++ b/tests/stdlib/tos.nim
@@ -43,6 +43,7 @@ true
 true
 true
 true
+
 '''
 """
 # test os path creation, iteration, and deletion
@@ -137,8 +138,69 @@ import times
 let tm = fromUnix(0) + 100.microseconds
 writeFile("a", "")
 setLastModificationTime("a", tm)
+
 when defined(macosx):
   echo "true"
 else:
   echo getLastModificationTime("a") == tm
 removeFile("a")
+
+when defined(Linux) or defined(osx):
+
+  block normalizedPath:
+    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 normalizedPath:
+    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"