|
1 From 3129c67db76bc8ee13a1edc38a6c25f9eddcbc6c Mon Sep 17 00:00:00 2001 |
|
2 From: Hiroshi Ioka <[email protected]> |
|
3 Date: Fri, 19 Aug 2016 09:37:19 +0900 |
|
4 Subject: [PATCH 10/38] [release-branch.go1.7] path/filepath: handle ".." in |
|
5 normalizing a path on Windows |
|
6 |
|
7 Current code assumes there are not ".." in the Clean(path). |
|
8 That's not true. Clean doesn't handle leading "..", so we need to stop |
|
9 normalization if we see "..". |
|
10 |
|
11 Fixes #16793 |
|
12 |
|
13 Change-Id: I0a7901bedac17f1210b134d593ebd9f5e8483775 |
|
14 Reviewed-on: https://go-review.googlesource.com/27410 |
|
15 Reviewed-by: Ian Lance Taylor <[email protected]> |
|
16 Reviewed-by: Alex Brainman <[email protected]> |
|
17 Run-TryBot: Ian Lance Taylor <[email protected]> |
|
18 TryBot-Result: Gobot Gobot <[email protected]> |
|
19 Reviewed-on: https://go-review.googlesource.com/28641 |
|
20 Reviewed-by: Brad Fitzpatrick <[email protected]> |
|
21 --- |
|
22 src/path/filepath/export_windows_test.go | 5 +- |
|
23 src/path/filepath/path_test.go | 32 +++++++++- |
|
24 src/path/filepath/path_windows_test.go | 101 ++++++++++++++++++++++++++++++- |
|
25 src/path/filepath/symlink_windows.go | 29 ++++++++- |
|
26 4 files changed, 159 insertions(+), 8 deletions(-) |
|
27 |
|
28 diff --git a/src/path/filepath/export_windows_test.go b/src/path/filepath/export_windows_test.go |
|
29 index 8ca007f..a7e2e64 100644 |
|
30 --- a/src/path/filepath/export_windows_test.go |
|
31 +++ b/src/path/filepath/export_windows_test.go |
|
32 @@ -4,4 +4,7 @@ |
|
33 |
|
34 package filepath |
|
35 |
|
36 -var ToNorm = toNorm |
|
37 +var ( |
|
38 + ToNorm = toNorm |
|
39 + NormBase = normBase |
|
40 +) |
|
41 diff --git a/src/path/filepath/path_test.go b/src/path/filepath/path_test.go |
|
42 index 1a4a9d2..a3990e2 100644 |
|
43 --- a/src/path/filepath/path_test.go |
|
44 +++ b/src/path/filepath/path_test.go |
|
45 @@ -840,7 +840,7 @@ func TestEvalSymlinks(t *testing.T) { |
|
46 if p, err := filepath.EvalSymlinks(path); err != nil { |
|
47 t.Errorf("EvalSymlinks(%q) error: %v", d.path, err) |
|
48 } else if filepath.Clean(p) != filepath.Clean(dest) { |
|
49 - t.Errorf("Clean(%q)=%q, want %q", path, p, dest) |
|
50 + t.Errorf("EvalSymlinks(%q)=%q, want %q", path, p, dest) |
|
51 } |
|
52 |
|
53 // test EvalSymlinks(".") |
|
54 @@ -872,6 +872,34 @@ func TestEvalSymlinks(t *testing.T) { |
|
55 t.Errorf(`EvalSymlinks(".") in %q directory returns %q, want "." or %q`, d.path, p, want) |
|
56 }() |
|
57 |
|
58 + // test EvalSymlinks(".."+path) |
|
59 + func() { |
|
60 + defer func() { |
|
61 + err := os.Chdir(wd) |
|
62 + if err != nil { |
|
63 + t.Fatal(err) |
|
64 + } |
|
65 + }() |
|
66 + |
|
67 + err := os.Chdir(simpleJoin(tmpDir, "test")) |
|
68 + if err != nil { |
|
69 + t.Error(err) |
|
70 + return |
|
71 + } |
|
72 + |
|
73 + path := simpleJoin("..", d.path) |
|
74 + dest := simpleJoin("..", d.dest) |
|
75 + if filepath.IsAbs(d.dest) || os.IsPathSeparator(d.dest[0]) { |
|
76 + dest = d.dest |
|
77 + } |
|
78 + |
|
79 + if p, err := filepath.EvalSymlinks(path); err != nil { |
|
80 + t.Errorf("EvalSymlinks(%q) error: %v", d.path, err) |
|
81 + } else if filepath.Clean(p) != filepath.Clean(dest) { |
|
82 + t.Errorf("EvalSymlinks(%q)=%q, want %q", path, p, dest) |
|
83 + } |
|
84 + }() |
|
85 + |
|
86 // test EvalSymlinks where parameter is relative path |
|
87 func() { |
|
88 defer func() { |
|
89 @@ -889,7 +917,7 @@ func TestEvalSymlinks(t *testing.T) { |
|
90 if p, err := filepath.EvalSymlinks(d.path); err != nil { |
|
91 t.Errorf("EvalSymlinks(%q) error: %v", d.path, err) |
|
92 } else if filepath.Clean(p) != filepath.Clean(d.dest) { |
|
93 - t.Errorf("Clean(%q)=%q, want %q", d.path, p, d.dest) |
|
94 + t.Errorf("EvalSymlinks(%q)=%q, want %q", d.path, p, d.dest) |
|
95 } |
|
96 }() |
|
97 } |
|
98 diff --git a/src/path/filepath/path_windows_test.go b/src/path/filepath/path_windows_test.go |
|
99 index b47cdfd..9c82a0b 100644 |
|
100 --- a/src/path/filepath/path_windows_test.go |
|
101 +++ b/src/path/filepath/path_windows_test.go |
|
102 @@ -329,9 +329,106 @@ func TestToNorm(t *testing.T) { |
|
103 for _, test := range tests { |
|
104 got, err := filepath.ToNorm(test.arg, stubBase) |
|
105 if err != nil { |
|
106 - t.Errorf("unexpected toNorm error, arg: %s, err: %v\n", test.arg, err) |
|
107 + t.Errorf("toNorm(%s) failed: %v\n", test.arg, err) |
|
108 } else if got != test.want { |
|
109 - t.Errorf("toNorm error, arg: %s, want: %s, got: %s\n", test.arg, test.want, got) |
|
110 + t.Errorf("toNorm(%s) returns %s, but %s expected\n", test.arg, got, test.want) |
|
111 + } |
|
112 + } |
|
113 + |
|
114 + testPath := `{{tmp}}\test\foo\bar` |
|
115 + |
|
116 + testsDir := []struct { |
|
117 + wd string |
|
118 + arg string |
|
119 + want string |
|
120 + }{ |
|
121 + // test absolute paths |
|
122 + {".", `{{tmp}}\test\foo\bar`, `{{tmp}}\test\foo\bar`}, |
|
123 + {".", `{{tmp}}\.\test/foo\bar`, `{{tmp}}\test\foo\bar`}, |
|
124 + {".", `{{tmp}}\test\..\test\foo\bar`, `{{tmp}}\test\foo\bar`}, |
|
125 + {".", `{{tmp}}\TEST\FOO\BAR`, `{{tmp}}\test\foo\bar`}, |
|
126 + |
|
127 + // test relative paths begin with drive letter |
|
128 + {`{{tmp}}\test`, `{{tmpvol}}.`, `{{tmpvol}}.`}, |
|
129 + {`{{tmp}}\test`, `{{tmpvol}}..`, `{{tmpvol}}..`}, |
|
130 + {`{{tmp}}\test`, `{{tmpvol}}foo\bar`, `{{tmpvol}}foo\bar`}, |
|
131 + {`{{tmp}}\test`, `{{tmpvol}}.\foo\bar`, `{{tmpvol}}foo\bar`}, |
|
132 + {`{{tmp}}\test`, `{{tmpvol}}foo\..\foo\bar`, `{{tmpvol}}foo\bar`}, |
|
133 + {`{{tmp}}\test`, `{{tmpvol}}FOO\BAR`, `{{tmpvol}}foo\bar`}, |
|
134 + |
|
135 + // test relative paths begin with '\' |
|
136 + {".", `{{tmpnovol}}\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`}, |
|
137 + {".", `{{tmpnovol}}\.\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`}, |
|
138 + {".", `{{tmpnovol}}\test\..\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`}, |
|
139 + {".", `{{tmpnovol}}\TEST\FOO\BAR`, `{{tmpnovol}}\test\foo\bar`}, |
|
140 + |
|
141 + // test relative paths begin without '\' |
|
142 + {`{{tmp}}\test`, ".", `.`}, |
|
143 + {`{{tmp}}\test`, "..", `..`}, |
|
144 + {`{{tmp}}\test`, `foo\bar`, `foo\bar`}, |
|
145 + {`{{tmp}}\test`, `.\foo\bar`, `foo\bar`}, |
|
146 + {`{{tmp}}\test`, `foo\..\foo\bar`, `foo\bar`}, |
|
147 + {`{{tmp}}\test`, `FOO\BAR`, `foo\bar`}, |
|
148 + } |
|
149 + |
|
150 + cwd, err := os.Getwd() |
|
151 + if err != nil { |
|
152 + t.Fatal(err) |
|
153 + } |
|
154 + |
|
155 + defer func() { |
|
156 + err := os.Chdir(cwd) |
|
157 + if err != nil { |
|
158 + t.Fatal(err) |
|
159 + } |
|
160 + }() |
|
161 + |
|
162 + tmp, err := ioutil.TempDir("", "testToNorm") |
|
163 + if err != nil { |
|
164 + t.Fatal(err) |
|
165 + } |
|
166 + defer os.RemoveAll(tmp) |
|
167 + |
|
168 + // ioutil.TempDir might return "non-canonical" name. |
|
169 + tmp, err = filepath.EvalSymlinks(tmp) |
|
170 + if err != nil { |
|
171 + t.Fatal(err) |
|
172 + } |
|
173 + |
|
174 + err = os.MkdirAll(strings.Replace(testPath, "{{tmp}}", tmp, -1), 0777) |
|
175 + if err != nil { |
|
176 + t.Fatal(err) |
|
177 + } |
|
178 + |
|
179 + tmpVol := filepath.VolumeName(tmp) |
|
180 + tmpNoVol := tmp[len(tmpVol):] |
|
181 + |
|
182 + for _, test := range testsDir { |
|
183 + wd := strings.Replace(strings.Replace(strings.Replace(test.wd, "{{tmp}}", tmp, -1), "{{tmpvol}}", tmpVol, -1), "{{tmpnovol}}", tmpNoVol, -1) |
|
184 + arg := strings.Replace(strings.Replace(strings.Replace(test.arg, "{{tmp}}", tmp, -1), "{{tmpvol}}", tmpVol, -1), "{{tmpnovol}}", tmpNoVol, -1) |
|
185 + want := strings.Replace(strings.Replace(strings.Replace(test.want, "{{tmp}}", tmp, -1), "{{tmpvol}}", tmpVol, -1), "{{tmpnovol}}", tmpNoVol, -1) |
|
186 + |
|
187 + if test.wd == "." { |
|
188 + err := os.Chdir(cwd) |
|
189 + if err != nil { |
|
190 + t.Error(err) |
|
191 + |
|
192 + continue |
|
193 + } |
|
194 + } else { |
|
195 + err := os.Chdir(wd) |
|
196 + if err != nil { |
|
197 + t.Error(err) |
|
198 + |
|
199 + continue |
|
200 + } |
|
201 + } |
|
202 + |
|
203 + got, err := filepath.ToNorm(arg, filepath.NormBase) |
|
204 + if err != nil { |
|
205 + t.Errorf("toNorm(%s) failed: %v (wd=%s)\n", arg, err, wd) |
|
206 + } else if got != want { |
|
207 + t.Errorf("toNorm(%s) returns %s, but %s expected (wd=%s)\n", arg, got, want, wd) |
|
208 } |
|
209 } |
|
210 } |
|
211 diff --git a/src/path/filepath/symlink_windows.go b/src/path/filepath/symlink_windows.go |
|
212 index 2433528..bb05aab 100644 |
|
213 --- a/src/path/filepath/symlink_windows.go |
|
214 +++ b/src/path/filepath/symlink_windows.go |
|
215 @@ -22,7 +22,7 @@ func normVolumeName(path string) string { |
|
216 return strings.ToUpper(volume) |
|
217 } |
|
218 |
|
219 -// normBase retruns the last element of path. |
|
220 +// normBase returns the last element of path with correct case. |
|
221 func normBase(path string) (string, error) { |
|
222 p, err := syscall.UTF16PtrFromString(path) |
|
223 if err != nil { |
|
224 @@ -40,7 +40,24 @@ func normBase(path string) (string, error) { |
|
225 return syscall.UTF16ToString(data.FileName[:]), nil |
|
226 } |
|
227 |
|
228 -func toNorm(path string, base func(string) (string, error)) (string, error) { |
|
229 +// baseIsDotDot returns whether the last element of path is "..". |
|
230 +// The given path should be 'Clean'-ed in advance. |
|
231 +func baseIsDotDot(path string) bool { |
|
232 + i := strings.LastIndexByte(path, Separator) |
|
233 + return path[i+1:] == ".." |
|
234 +} |
|
235 + |
|
236 +// toNorm returns the normalized path that is guranteed to be unique. |
|
237 +// It should accept the following formats: |
|
238 +// * UNC paths (e.g \\server\share\foo\bar) |
|
239 +// * absolute paths (e.g C:\foo\bar) |
|
240 +// * relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.) |
|
241 +// * relative paths begin with '\' (e.g \foo\bar) |
|
242 +// * relative paths begin without '\' (e.g foo\bar, ..\foo\bar, .., .) |
|
243 +// The returned normalized path will be in the same form (of 5 listed above) as the input path. |
|
244 +// If two paths A and B are indicating the same file with the same format, toNorm(A) should be equal to toNorm(B). |
|
245 +// The normBase parameter should be equal to the normBase func, except for in tests. See docs on the normBase func. |
|
246 +func toNorm(path string, normBase func(string) (string, error)) (string, error) { |
|
247 if path == "" { |
|
248 return path, nil |
|
249 } |
|
250 @@ -58,7 +75,13 @@ func toNorm(path string, base func(string) (string, error)) (string, error) { |
|
251 var normPath string |
|
252 |
|
253 for { |
|
254 - name, err := base(volume + path) |
|
255 + if baseIsDotDot(path) { |
|
256 + normPath = path + `\` + normPath |
|
257 + |
|
258 + break |
|
259 + } |
|
260 + |
|
261 + name, err := normBase(volume + path) |
|
262 if err != nil { |
|
263 return "", err |
|
264 } |
|
265 -- |
|
266 2.7.4 |
|
267 |