Skip to content

Commit 6e43407

Browse files
committed
path/filepath: don't drop .. elements when cleaning invalid Windows paths
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 #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]>
1 parent fe1daf2 commit 6e43407

File tree

2 files changed

+25
-12
lines changed

2 files changed

+25
-12
lines changed

src/path/filepath/path.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"io/fs"
1717
"os"
1818
"runtime"
19+
"slices"
1920
"sort"
2021
"strings"
2122
)
@@ -52,6 +53,11 @@ func (b *lazybuf) append(c byte) {
5253
b.w++
5354
}
5455

56+
func (b *lazybuf) prepend(prefix ...byte) {
57+
b.buf = slices.Insert(b.buf, 0, prefix...)
58+
b.w += len(prefix)
59+
}
60+
5561
func (b *lazybuf) string() string {
5662
if b.buf == nil {
5763
return b.volAndPath[:b.volLen+b.w]
@@ -150,18 +156,6 @@ func Clean(path string) string {
150156
if rooted && out.w != 1 || !rooted && out.w != 0 {
151157
out.append(Separator)
152158
}
153-
// If a ':' appears in the path element at the start of a Windows path,
154-
// insert a .\ at the beginning to avoid converting relative paths
155-
// like a/../c: into c:.
156-
if runtime.GOOS == "windows" && out.w == 0 && out.volLen == 0 && r != 0 {
157-
for i := r; i < n && !os.IsPathSeparator(path[i]); i++ {
158-
if path[i] == ':' {
159-
out.append('.')
160-
out.append(Separator)
161-
break
162-
}
163-
}
164-
}
165159
// copy element
166160
for ; r < n && !os.IsPathSeparator(path[r]); r++ {
167161
out.append(path[r])
@@ -174,6 +168,21 @@ func Clean(path string) string {
174168
out.append('.')
175169
}
176170

171+
if runtime.GOOS == "windows" && out.volLen == 0 && out.buf != nil {
172+
// If a ':' appears in the path element at the start of a Windows path,
173+
// insert a .\ at the beginning to avoid converting relative paths
174+
// like a/../c: into c:.
175+
for _, c := range out.buf {
176+
if os.IsPathSeparator(c) {
177+
break
178+
}
179+
if c == ':' {
180+
out.prepend('.', Separator)
181+
break
182+
}
183+
}
184+
}
185+
177186
return FromSlash(out.string())
178187
}
179188

src/path/filepath/path_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ var cleantests = []PathTest{
6767
{"/abc/def/../../..", "/"},
6868
{"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
6969
{"/../abc", "/abc"},
70+
{"a/../b:/../../c", `../c`},
7071

7172
// Combinations
7273
{"abc/./../def", "def"},
@@ -89,6 +90,7 @@ var wincleantests = []PathTest{
8990
{`c:\abc\def\..\..`, `c:\`},
9091
{`c:\..\abc`, `c:\abc`},
9192
{`c:..\abc`, `c:..\abc`},
93+
{`c:\b:\..\..\..\d`, `c:\d`},
9294
{`\`, `\`},
9395
{`/`, `\`},
9496
{`\\i\..\c$`, `\\i\..\c$`},
@@ -169,6 +171,7 @@ var islocaltests = []IsLocalTest{
169171
{"a/", true},
170172
{"a/.", true},
171173
{"a/./b/./c", true},
174+
{`a/../b:/../../c`, false},
172175
}
173176

174177
var winislocaltests = []IsLocalTest{
@@ -380,6 +383,7 @@ var winjointests = []JoinTest{
380383
{[]string{`\\a`, `b`, `c`}, `\\a\b\c`},
381384
{[]string{`\\a\`, `b`, `c`}, `\\a\b\c`},
382385
{[]string{`//`, `a`}, `\\a`},
386+
{[]string{`a:\b\c`, `x\..\y:\..\..\z`}, `a:\b\z`},
383387
}
384388

385389
func TestJoin(t *testing.T) {

0 commit comments

Comments
 (0)