diff --git a/backend/drive/drive.go b/backend/drive/drive.go index 93fab7f8d..7bd0c41e5 100644 --- a/backend/drive/drive.go +++ b/backend/drive/drive.go @@ -18,7 +18,6 @@ import ( "net/http" "os" "path" - "regexp" "sort" "strconv" "strings" @@ -3431,13 +3430,12 @@ func (f *Fs) Command(ctx context.Context, name string, arg []string, opt map[str if err != nil { return nil, err } - re := regexp.MustCompile(`[^\w\p{L}\p{N}. -]+`) if _, ok := opt["config"]; ok { lines := []string{} upstreams := []string{} names := make(map[string]struct{}, len(drives)) for i, drive := range drives { - name := re.ReplaceAllString(drive.Name, "_") + name := fspath.MakeConfigName(drive.Name) for { if _, found := names[name]; !found { break diff --git a/fs/fspath/path.go b/fs/fspath/path.go index 2c9c42b00..5a6775207 100644 --- a/fs/fspath/path.go +++ b/fs/fspath/path.go @@ -13,7 +13,8 @@ import ( ) const ( - configNameRe = `[\w\p{L}\p{N}.]+(?:[ -]+[\w\p{L}\p{N}.-]+)*` // don't allow it to start with `-` as it complicates usage (#4261) + configNameRe = `[\w\p{L}\p{N}.]+(?:[ -]+[\w\p{L}\p{N}.-]+)*` // May contain Unicode numbers and letters, as well as `_`, `-`, `.` and space, but not start with `-` (it complicates usage, see #4261) or space, and not end with space + illegalPartOfConfigNameRe = `^[ -]+|[^\w\p{L}\p{N}. -]+|[ ]+$` ) var ( @@ -32,6 +33,9 @@ var ( // configNameMatcher is a pattern to match an rclone config name configNameMatcher = regexp.MustCompile(`^` + configNameRe + `$`) + // illegalPartOfConfigNameMatcher is a pattern to match a sequence of characters not allowed in an rclone config name + illegalPartOfConfigNameMatcher = regexp.MustCompile(illegalPartOfConfigNameRe) + // remoteNameMatcher is a pattern to match an rclone remote name at the start of a config remoteNameMatcher = regexp.MustCompile(`^:?` + configNameRe + `(?::$|,)`) ) @@ -44,6 +48,21 @@ func CheckConfigName(configName string) error { return nil } +// MakeConfigName makes an input into something legal to be used as a config name. +// Returns a string where any sequences of illegal characters are replaced with +// a single underscore. If the input is already valid as a config name, it is +// returned unchanged. If the input is an empty string, a single underscore is +// returned. +func MakeConfigName(name string) string { + if name == "" { + return "_" + } + if configNameMatcher.MatchString(name) { + return name + } + return illegalPartOfConfigNameMatcher.ReplaceAllString(name, "_") +} + // checkRemoteName returns an error if remoteName is invalid func checkRemoteName(remoteName string) error { if remoteName == ":" || remoteName == "::" { diff --git a/fs/fspath/path_test.go b/fs/fspath/path_test.go index ad58ef399..d1828c102 100644 --- a/fs/fspath/path_test.go +++ b/fs/fspath/path_test.go @@ -19,34 +19,38 @@ var ( func TestCheckConfigName(t *testing.T) { for _, test := range []struct { - in string - want error + in string + problem error + fixed string }{ - {"remote", nil}, - {"REMOTE", nil}, - {"", errInvalidCharacters}, - {":remote:", errInvalidCharacters}, - {"remote:", errInvalidCharacters}, - {"rem:ote", errInvalidCharacters}, - {"rem/ote", errInvalidCharacters}, - {"rem\\ote", errInvalidCharacters}, - {"[remote", errInvalidCharacters}, - {"*", errInvalidCharacters}, - {"-remote", errInvalidCharacters}, - {"r-emote-", nil}, - {"_rem_ote_", nil}, - {".", nil}, - {"..", nil}, - {".r.e.m.o.t.e.", nil}, - {"rem ote", nil}, - {"blåbær", nil}, - {"chữ Quốc ngữ", nil}, - {"remote ", errInvalidCharacters}, - {" remote", errInvalidCharacters}, - {" remote ", errInvalidCharacters}, + {"remote", nil, "remote"}, + {"REMOTE", nil, "REMOTE"}, + {"", errInvalidCharacters, "_"}, + {":remote:", errInvalidCharacters, "_remote_"}, + {"remote:", errInvalidCharacters, "remote_"}, + {"rem:ote", errInvalidCharacters, "rem_ote"}, + {"rem/ote", errInvalidCharacters, "rem_ote"}, + {"rem\\ote", errInvalidCharacters, "rem_ote"}, + {"[remote", errInvalidCharacters, "_remote"}, + {"*", errInvalidCharacters, "_"}, + {"-remote", errInvalidCharacters, "_remote"}, + {"r-emote-", nil, "r-emote-"}, + {"---rem:::ote???", errInvalidCharacters, "_rem_ote_"}, + {"_rem_ote_", nil, "_rem_ote_"}, + {".", nil, "."}, + {"..", nil, ".."}, + {".r.e.m.o.t.e.", nil, ".r.e.m.o.t.e."}, + {"rem ote", nil, "rem ote"}, + {"blåbær", nil, "blåbær"}, + {"chữ Quốc ngữ", nil, "chữ Quốc ngữ"}, + {"remote ", errInvalidCharacters, "remote_"}, + {" remote", errInvalidCharacters, "_remote"}, + {" remote ", errInvalidCharacters, "_remote_"}, } { - got := CheckConfigName(test.in) - assert.Equal(t, test.want, got, test.in) + problem := CheckConfigName(test.in) + assert.Equal(t, test.problem, problem, test.in) + fixed := MakeConfigName(test.in) + assert.Equal(t, test.fixed, fixed, test.in) } }