Skip to content

Commit 3129c67

Browse files
hirochachachabroady
authored andcommitted
[release-branch.go1.7] path/filepath: handle ".." in normalizing a path on Windows
Current code assumes there are not ".." in the Clean(path). That's not true. Clean doesn't handle leading "..", so we need to stop normalization if we see "..". Fixes #16793 Change-Id: I0a7901bedac17f1210b134d593ebd9f5e8483775 Reviewed-on: https://go-review.googlesource.com/27410 Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Alex Brainman <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-on: https://go-review.googlesource.com/28641 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent bb87068 commit 3129c67

File tree

4 files changed

+159
-8
lines changed

4 files changed

+159
-8
lines changed

src/path/filepath/export_windows_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@
44

55
package filepath
66

7-
var ToNorm = toNorm
7+
var (
8+
ToNorm = toNorm
9+
NormBase = normBase
10+
)

src/path/filepath/path_test.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,7 @@ func TestEvalSymlinks(t *testing.T) {
840840
if p, err := filepath.EvalSymlinks(path); err != nil {
841841
t.Errorf("EvalSymlinks(%q) error: %v", d.path, err)
842842
} else if filepath.Clean(p) != filepath.Clean(dest) {
843-
t.Errorf("Clean(%q)=%q, want %q", path, p, dest)
843+
t.Errorf("EvalSymlinks(%q)=%q, want %q", path, p, dest)
844844
}
845845

846846
// test EvalSymlinks(".")
@@ -872,6 +872,34 @@ func TestEvalSymlinks(t *testing.T) {
872872
t.Errorf(`EvalSymlinks(".") in %q directory returns %q, want "." or %q`, d.path, p, want)
873873
}()
874874

875+
// test EvalSymlinks(".."+path)
876+
func() {
877+
defer func() {
878+
err := os.Chdir(wd)
879+
if err != nil {
880+
t.Fatal(err)
881+
}
882+
}()
883+
884+
err := os.Chdir(simpleJoin(tmpDir, "test"))
885+
if err != nil {
886+
t.Error(err)
887+
return
888+
}
889+
890+
path := simpleJoin("..", d.path)
891+
dest := simpleJoin("..", d.dest)
892+
if filepath.IsAbs(d.dest) || os.IsPathSeparator(d.dest[0]) {
893+
dest = d.dest
894+
}
895+
896+
if p, err := filepath.EvalSymlinks(path); err != nil {
897+
t.Errorf("EvalSymlinks(%q) error: %v", d.path, err)
898+
} else if filepath.Clean(p) != filepath.Clean(dest) {
899+
t.Errorf("EvalSymlinks(%q)=%q, want %q", path, p, dest)
900+
}
901+
}()
902+
875903
// test EvalSymlinks where parameter is relative path
876904
func() {
877905
defer func() {
@@ -889,7 +917,7 @@ func TestEvalSymlinks(t *testing.T) {
889917
if p, err := filepath.EvalSymlinks(d.path); err != nil {
890918
t.Errorf("EvalSymlinks(%q) error: %v", d.path, err)
891919
} else if filepath.Clean(p) != filepath.Clean(d.dest) {
892-
t.Errorf("Clean(%q)=%q, want %q", d.path, p, d.dest)
920+
t.Errorf("EvalSymlinks(%q)=%q, want %q", d.path, p, d.dest)
893921
}
894922
}()
895923
}

src/path/filepath/path_windows_test.go

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,9 +329,106 @@ func TestToNorm(t *testing.T) {
329329
for _, test := range tests {
330330
got, err := filepath.ToNorm(test.arg, stubBase)
331331
if err != nil {
332-
t.Errorf("unexpected toNorm error, arg: %s, err: %v\n", test.arg, err)
332+
t.Errorf("toNorm(%s) failed: %v\n", test.arg, err)
333333
} else if got != test.want {
334-
t.Errorf("toNorm error, arg: %s, want: %s, got: %s\n", test.arg, test.want, got)
334+
t.Errorf("toNorm(%s) returns %s, but %s expected\n", test.arg, got, test.want)
335+
}
336+
}
337+
338+
testPath := `{{tmp}}\test\foo\bar`
339+
340+
testsDir := []struct {
341+
wd string
342+
arg string
343+
want string
344+
}{
345+
// test absolute paths
346+
{".", `{{tmp}}\test\foo\bar`, `{{tmp}}\test\foo\bar`},
347+
{".", `{{tmp}}\.\test/foo\bar`, `{{tmp}}\test\foo\bar`},
348+
{".", `{{tmp}}\test\..\test\foo\bar`, `{{tmp}}\test\foo\bar`},
349+
{".", `{{tmp}}\TEST\FOO\BAR`, `{{tmp}}\test\foo\bar`},
350+
351+
// test relative paths begin with drive letter
352+
{`{{tmp}}\test`, `{{tmpvol}}.`, `{{tmpvol}}.`},
353+
{`{{tmp}}\test`, `{{tmpvol}}..`, `{{tmpvol}}..`},
354+
{`{{tmp}}\test`, `{{tmpvol}}foo\bar`, `{{tmpvol}}foo\bar`},
355+
{`{{tmp}}\test`, `{{tmpvol}}.\foo\bar`, `{{tmpvol}}foo\bar`},
356+
{`{{tmp}}\test`, `{{tmpvol}}foo\..\foo\bar`, `{{tmpvol}}foo\bar`},
357+
{`{{tmp}}\test`, `{{tmpvol}}FOO\BAR`, `{{tmpvol}}foo\bar`},
358+
359+
// test relative paths begin with '\'
360+
{".", `{{tmpnovol}}\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
361+
{".", `{{tmpnovol}}\.\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
362+
{".", `{{tmpnovol}}\test\..\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
363+
{".", `{{tmpnovol}}\TEST\FOO\BAR`, `{{tmpnovol}}\test\foo\bar`},
364+
365+
// test relative paths begin without '\'
366+
{`{{tmp}}\test`, ".", `.`},
367+
{`{{tmp}}\test`, "..", `..`},
368+
{`{{tmp}}\test`, `foo\bar`, `foo\bar`},
369+
{`{{tmp}}\test`, `.\foo\bar`, `foo\bar`},
370+
{`{{tmp}}\test`, `foo\..\foo\bar`, `foo\bar`},
371+
{`{{tmp}}\test`, `FOO\BAR`, `foo\bar`},
372+
}
373+
374+
cwd, err := os.Getwd()
375+
if err != nil {
376+
t.Fatal(err)
377+
}
378+
379+
defer func() {
380+
err := os.Chdir(cwd)
381+
if err != nil {
382+
t.Fatal(err)
383+
}
384+
}()
385+
386+
tmp, err := ioutil.TempDir("", "testToNorm")
387+
if err != nil {
388+
t.Fatal(err)
389+
}
390+
defer os.RemoveAll(tmp)
391+
392+
// ioutil.TempDir might return "non-canonical" name.
393+
tmp, err = filepath.EvalSymlinks(tmp)
394+
if err != nil {
395+
t.Fatal(err)
396+
}
397+
398+
err = os.MkdirAll(strings.Replace(testPath, "{{tmp}}", tmp, -1), 0777)
399+
if err != nil {
400+
t.Fatal(err)
401+
}
402+
403+
tmpVol := filepath.VolumeName(tmp)
404+
tmpNoVol := tmp[len(tmpVol):]
405+
406+
for _, test := range testsDir {
407+
wd := strings.Replace(strings.Replace(strings.Replace(test.wd, "{{tmp}}", tmp, -1), "{{tmpvol}}", tmpVol, -1), "{{tmpnovol}}", tmpNoVol, -1)
408+
arg := strings.Replace(strings.Replace(strings.Replace(test.arg, "{{tmp}}", tmp, -1), "{{tmpvol}}", tmpVol, -1), "{{tmpnovol}}", tmpNoVol, -1)
409+
want := strings.Replace(strings.Replace(strings.Replace(test.want, "{{tmp}}", tmp, -1), "{{tmpvol}}", tmpVol, -1), "{{tmpnovol}}", tmpNoVol, -1)
410+
411+
if test.wd == "." {
412+
err := os.Chdir(cwd)
413+
if err != nil {
414+
t.Error(err)
415+
416+
continue
417+
}
418+
} else {
419+
err := os.Chdir(wd)
420+
if err != nil {
421+
t.Error(err)
422+
423+
continue
424+
}
425+
}
426+
427+
got, err := filepath.ToNorm(arg, filepath.NormBase)
428+
if err != nil {
429+
t.Errorf("toNorm(%s) failed: %v (wd=%s)\n", arg, err, wd)
430+
} else if got != want {
431+
t.Errorf("toNorm(%s) returns %s, but %s expected (wd=%s)\n", arg, got, want, wd)
335432
}
336433
}
337434
}

src/path/filepath/symlink_windows.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func normVolumeName(path string) string {
2222
return strings.ToUpper(volume)
2323
}
2424

25-
// normBase retruns the last element of path.
25+
// normBase returns the last element of path with correct case.
2626
func normBase(path string) (string, error) {
2727
p, err := syscall.UTF16PtrFromString(path)
2828
if err != nil {
@@ -40,7 +40,24 @@ func normBase(path string) (string, error) {
4040
return syscall.UTF16ToString(data.FileName[:]), nil
4141
}
4242

43-
func toNorm(path string, base func(string) (string, error)) (string, error) {
43+
// baseIsDotDot returns whether the last element of path is "..".
44+
// The given path should be 'Clean'-ed in advance.
45+
func baseIsDotDot(path string) bool {
46+
i := strings.LastIndexByte(path, Separator)
47+
return path[i+1:] == ".."
48+
}
49+
50+
// toNorm returns the normalized path that is guranteed to be unique.
51+
// It should accept the following formats:
52+
// * UNC paths (e.g \\server\share\foo\bar)
53+
// * absolute paths (e.g C:\foo\bar)
54+
// * relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.)
55+
// * relative paths begin with '\' (e.g \foo\bar)
56+
// * relative paths begin without '\' (e.g foo\bar, ..\foo\bar, .., .)
57+
// The returned normalized path will be in the same form (of 5 listed above) as the input path.
58+
// If two paths A and B are indicating the same file with the same format, toNorm(A) should be equal to toNorm(B).
59+
// The normBase parameter should be equal to the normBase func, except for in tests. See docs on the normBase func.
60+
func toNorm(path string, normBase func(string) (string, error)) (string, error) {
4461
if path == "" {
4562
return path, nil
4663
}
@@ -58,7 +75,13 @@ func toNorm(path string, base func(string) (string, error)) (string, error) {
5875
var normPath string
5976

6077
for {
61-
name, err := base(volume + path)
78+
if baseIsDotDot(path) {
79+
normPath = path + `\` + normPath
80+
81+
break
82+
}
83+
84+
name, err := normBase(volume + path)
6285
if err != nil {
6386
return "", err
6487
}

0 commit comments

Comments
 (0)