diff --git a/fs/config/config.go b/fs/config/config.go index fe0e36312..722cd7eb3 100644 --- a/fs/config/config.go +++ b/fs/config/config.go @@ -22,6 +22,7 @@ import ( "github.com/rclone/rclone/fs/config/obscure" "github.com/rclone/rclone/fs/fspath" "github.com/rclone/rclone/fs/rc" + "github.com/rclone/rclone/lib/file" "github.com/rclone/rclone/lib/random" ) @@ -226,16 +227,20 @@ func GetConfigPath() string { // // Checks for empty string, os null device, or special path, all of which indicates in-memory config. func SetConfigPath(path string) (err error) { + var cfgPath string if path == "" || path == os.DevNull { - configPath = "" + cfgPath = "" + } else if err = file.IsReserved(path); err != nil { + return err } else { - if configPath, err = filepath.Abs(path); err != nil { + if cfgPath, err = filepath.Abs(path); err != nil { return err } - if configPath == noConfigPath { - configPath = "" + if cfgPath == noConfigPath { + cfgPath = "" } } + configPath = cfgPath return nil } diff --git a/lib/file/file_other.go b/lib/file/file_other.go index 16de15847..5094cd6a1 100644 --- a/lib/file/file_other.go +++ b/lib/file/file_other.go @@ -13,3 +13,8 @@ import "os" // Under both Unix and Windows this will allow open files to be // renamed and or deleted. var OpenFile = os.OpenFile + +// IsReserved checks if path contains a reserved name +func IsReserved(path string) error { + return nil +} diff --git a/lib/file/file_test.go b/lib/file/file_test.go index 0f4e05121..4aef72650 100644 --- a/lib/file/file_test.go +++ b/lib/file/file_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path" + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -152,3 +153,29 @@ func TestOpenFileOperations(t *testing.T) { }) } + +// Smoke test the IsReserved function +func TestIsReserved(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("Skipping test on !windows") + } + // Regular name + require.NoError(t, IsReserved("readme.txt")) + require.NoError(t, IsReserved("some/path/readme.txt")) + // Empty + require.Error(t, IsReserved("")) + // Separators only + require.Error(t, IsReserved("/")) + require.Error(t, IsReserved("////")) + require.Error(t, IsReserved("./././././")) + // Legacy device name + require.Error(t, IsReserved("NUL")) + require.Error(t, IsReserved("nul")) + require.Error(t, IsReserved("Nul")) + require.Error(t, IsReserved("NUL.txt")) + require.Error(t, IsReserved("some/path/to/nul.txt")) + require.NoError(t, IsReserved("NULL")) + // Name end with a space or a period + require.Error(t, IsReserved("test.")) + require.Error(t, IsReserved("test ")) +} diff --git a/lib/file/file_windows.go b/lib/file/file_windows.go index 29a6fad0f..dec7e0a36 100644 --- a/lib/file/file_windows.go +++ b/lib/file/file_windows.go @@ -3,7 +3,10 @@ package file import ( + "errors" "os" + "path/filepath" + "regexp" "syscall" ) @@ -64,3 +67,36 @@ func OpenFile(path string, mode int, perm os.FileMode) (*os.File, error) { } return os.NewFile(uintptr(h), path), nil } + +// IsReserved checks if path contains a reserved name +func IsReserved(path string) error { + if path == "" { + return errors.New("path is empty") + } + base := filepath.Base(path) + // If the path is empty or reduces to ".", Base returns ".". + if base == "." { + return errors.New("path is '.'") + } + // If the path consists entirely of separators, Base returns a single separator. + if base == string(filepath.Separator) { + return errors.New("path consists entirely of separators") + } + // Do not end a file or directory name with a space or a period. Although the underlying + // file system may support such names, the Windows shell and user interface does not. + // (https://docs.microsoft.com/en-gb/windows/win32/fileio/naming-a-file) + suffix := base[len(base)-1] + switch suffix { + case ' ': + return errors.New("base file name ends with a space") + case '.': + return errors.New("base file name ends with a period") + } + // Do not use names of legacy (DOS) devices, not even as basename without extension, + // as this will refer to the actual device. + // (https://docs.microsoft.com/en-gb/windows/win32/fileio/naming-a-file) + if reserved, _ := regexp.MatchString(`^(?i:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)`, base); reserved { + return errors.New("base file name is reserved windows device name (CON, PRN, AUX, NUL, COM[1-9], LPT[1-9])") + } + return nil +}