Skip to content

Commit

Permalink
path/filepath: don't drop .. elements when cleaning invalid Windows p…
Browse files Browse the repository at this point in the history
…aths

Fix a bug where Clean could improperly drop .. elements from a
path on Windows, when the path contains elements containing a ':'.

For example, Clean("a/../b:/../../c") now correctly returns "..\c"
rather than "c".

Fixes golang#61866

Change-Id: I97b0238953c183b2ce19ca89c14f26700008ea72
Reviewed-on: https://go-review.googlesource.com/c/go/+/517216
Run-TryBot: Damien Neil <[email protected]>
Reviewed-by: Bryan Mills <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Reviewed-by: Quim Muntal <[email protected]>
  • Loading branch information
neild committed Aug 8, 2023
1 parent fe1daf2 commit 6e43407
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 12 deletions.
33 changes: 21 additions & 12 deletions src/path/filepath/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"io/fs"
"os"
"runtime"
"slices"
"sort"
"strings"
)
Expand Down Expand Up @@ -52,6 +53,11 @@ func (b *lazybuf) append(c byte) {
b.w++
}

func (b *lazybuf) prepend(prefix ...byte) {
b.buf = slices.Insert(b.buf, 0, prefix...)
b.w += len(prefix)
}

func (b *lazybuf) string() string {
if b.buf == nil {
return b.volAndPath[:b.volLen+b.w]
Expand Down Expand Up @@ -150,18 +156,6 @@ func Clean(path string) string {
if rooted && out.w != 1 || !rooted && out.w != 0 {
out.append(Separator)
}
// If a ':' appears in the path element at the start of a Windows path,
// insert a .\ at the beginning to avoid converting relative paths
// like a/../c: into c:.
if runtime.GOOS == "windows" && out.w == 0 && out.volLen == 0 && r != 0 {
for i := r; i < n && !os.IsPathSeparator(path[i]); i++ {
if path[i] == ':' {
out.append('.')
out.append(Separator)
break
}
}
}
// copy element
for ; r < n && !os.IsPathSeparator(path[r]); r++ {
out.append(path[r])
Expand All @@ -174,6 +168,21 @@ func Clean(path string) string {
out.append('.')
}

if runtime.GOOS == "windows" && out.volLen == 0 && out.buf != nil {
// If a ':' appears in the path element at the start of a Windows path,
// insert a .\ at the beginning to avoid converting relative paths
// like a/../c: into c:.
for _, c := range out.buf {
if os.IsPathSeparator(c) {
break
}
if c == ':' {
out.prepend('.', Separator)
break
}
}
}

return FromSlash(out.string())
}

Expand Down
4 changes: 4 additions & 0 deletions src/path/filepath/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ var cleantests = []PathTest{
{"/abc/def/../../..", "/"},
{"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
{"/../abc", "/abc"},
{"a/../b:/../../c", `../c`},

// Combinations
{"abc/./../def", "def"},
Expand All @@ -89,6 +90,7 @@ var wincleantests = []PathTest{
{`c:\abc\def\..\..`, `c:\`},
{`c:\..\abc`, `c:\abc`},
{`c:..\abc`, `c:..\abc`},
{`c:\b:\..\..\..\d`, `c:\d`},
{`\`, `\`},
{`/`, `\`},
{`\\i\..\c$`, `\\i\..\c$`},
Expand Down Expand Up @@ -169,6 +171,7 @@ var islocaltests = []IsLocalTest{
{"a/", true},
{"a/.", true},
{"a/./b/./c", true},
{`a/../b:/../../c`, false},
}

var winislocaltests = []IsLocalTest{
Expand Down Expand Up @@ -380,6 +383,7 @@ var winjointests = []JoinTest{
{[]string{`\\a`, `b`, `c`}, `\\a\b\c`},
{[]string{`\\a\`, `b`, `c`}, `\\a\b\c`},
{[]string{`//`, `a`}, `\\a`},
{[]string{`a:\b\c`, `x\..\y:\..\..\z`}, `a:\b\z`},
}

func TestJoin(t *testing.T) {
Expand Down

0 comments on commit 6e43407

Please sign in to comment.