diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go index 48ce9617b..6742d06ad 100644 --- a/fstest/fstests/fstests.go +++ b/fstest/fstests/fstests.go @@ -36,7 +36,7 @@ var ( file2 = fstest.Item{ ModTime: fstest.Time("2001-02-03T04:05:10.123123123Z"), Path: `hello? sausage/êé/Hello, 世界/ " ' @ < > & ?/z.txt`, - WinPath: `hello_ sausage/êé/Hello, 世界/ _ ' @ _ _ _ _/z.txt`, + WinPath: `hello_ sausage/êé/Hello, 世界/ _ ' @ _ _ & _/z.txt`, } ) diff --git a/local/local.go b/local/local.go index 4e57407b7..56d31d130 100644 --- a/local/local.go +++ b/local/local.go @@ -167,30 +167,7 @@ func (f *FsLocal) cleanUtf8(name string) string { name = string([]rune(name)) } if runtime.GOOS == "windows" { - var name2 string - if strings.HasPrefix(name, `\\?\`) { - name2 = `\\?\` - strings.TrimPrefix(name, `\\?\`) - } - if strings.HasPrefix(name, `//?/`) { - name2 = `//?/` - strings.TrimPrefix(name, `//?/`) - } - name2 += strings.Map(func(r rune) rune { - switch r { - case '<', '>', '"', '|', '?', '*', ':': - return '_' - } - return r - }, name) - - if name2 != name { - if _, ok := f.warned[name]; !ok { - fs.Debug(f, "Replacing invalid UTF-8 characters in %q", name) - f.warned[name] = struct{}{} - } - name = name2 - } + name = cleanWindowsName(f, name) } return name } @@ -664,6 +641,43 @@ func uncPath(s string) string { return s } +// cleanWindowsName will clean invalid Windows characters +func cleanWindowsName(f *FsLocal, name string) string { + original := name + var name2 string + if strings.HasPrefix(name, `\\?\`) { + name2 = `\\?\` + name = strings.TrimPrefix(name, `\\?\`) + } + if strings.HasPrefix(name, `//?/`) { + name2 = `//?/` + name = strings.TrimPrefix(name, `//?/`) + } + // Colon is allowed as part of a drive name X:\ + colonAt := strings.Index(name, ":") + if colonAt > 0 && colonAt < 3 && len(name) > colonAt+1 { + // Copy to name2, which is unfiltered + name2 += name[0 : colonAt+1] + name = name[colonAt+1:] + } + + name2 += strings.Map(func(r rune) rune { + switch r { + case '<', '>', '"', '|', '?', '*', ':': + return '_' + } + return r + }, name) + + if name2 != original && f != nil { + if _, ok := f.warned[name]; !ok { + fs.Debug(f, "Replacing invalid characters in %q to %q", name, name2) + f.warned[name] = struct{}{} + } + } + return name2 +} + // Check the interfaces are satisfied var _ fs.Fs = &FsLocal{} var _ fs.Purger = &FsLocal{} diff --git a/local/tests_test.go b/local/tests_test.go index faf019465..7b2c6f8d1 100644 --- a/local/tests_test.go +++ b/local/tests_test.go @@ -50,3 +50,42 @@ func TestUncPaths(t *testing.T) { } } } + +var utf8Tests = [][2]string{ + [2]string{"ABC", "ABC"}, + [2]string{string([]byte{0x80}), "�"}, + [2]string{string([]byte{'a', 0x80, 'b'}), "a�b"}, +} + +func TestCleanUtf8(t *testing.T) { + f := &FsLocal{} + f.warned = make(map[string]struct{}) + for _, test := range utf8Tests { + got := f.cleanUtf8(test[0]) + expect := test[1] + if got != expect { + t.Fatalf("got %q, expected %q", got, expect) + } + } +} + +// Test Windows character replacements +var testsWindows = [][2]string{ + [2]string{`c:\temp`, `c:\temp`}, + [2]string{`\\?\UNC\theserver\dir\file.txt`, `\\?\UNC\theserver\dir\file.txt`}, + [2]string{`//?/UNC/theserver/dir\file.txt`, `//?/UNC/theserver/dir\file.txt`}, + [2]string{"c:/temp", "c:/temp"}, + [2]string{"/temp/file.txt", "/temp/file.txt"}, + [2]string{`!\"#¤%&/()=;:*^?+-`, "!\\_#¤%&/()=;__^_+-"}, + [2]string{`<>"|?*:&\<>"|?*:&\<>"|?*:&`, "_______&\\_______&\\_______&"}, +} + +func TestCleanWindows(t *testing.T) { + for _, test := range testsWindows { + got := cleanWindowsName(nil, test[0]) + expect := test[1] + if got != expect { + t.Fatalf("got %q, expected %q", got, expect) + } + } +}