summary refs log tree commit diff stats
path: root/lib/pure/os.nim
diff options
context:
space:
mode:
authorFederico Ceratto <federico.ceratto@gmail.com>2017-10-24 20:30:48 +0100
committerFederico Ceratto <federico.ceratto@gmail.com>2018-07-06 20:21:39 +0100
commitd65429d857f927b6110217611feb94b18ee7f2a4 (patch)
tree585b7a375e5c482c868d4d66cde55a99aded5f8a /lib/pure/os.nim
parent352b8a4844a4211fb7381f2f0d0b9e40dcb3cc72 (diff)
downloadNim-d65429d857f927b6110217611feb94b18ee7f2a4.tar.gz
Add normalizePath and tests
Diffstat (limited to 'lib/pure/os.nim')
-rw-r--r--lib/pure/os.nim45
1 files changed, 43 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