diff --git a/src/restic/backend/location/location.go b/src/restic/backend/location/location.go index 8cd3545c0..a5a070b12 100644 --- a/src/restic/backend/location/location.go +++ b/src/restic/backend/location/location.go @@ -10,6 +10,7 @@ import ( "restic/backend/s3" "restic/backend/sftp" "restic/backend/swift" + "restic/errors" ) // Location specifies the location of a repository, including the method of @@ -35,6 +36,36 @@ var parsers = []parser{ {"rest", rest.ParseConfig}, } +func isPath(s string) bool { + if strings.HasPrefix(s, "../") || strings.HasPrefix(s, `..\`) { + return true + } + + if strings.HasPrefix(s, "/") || strings.HasPrefix(s, `\`) { + return true + } + + if len(s) < 3 { + return false + } + + // check for drive paths + drive := s[0] + if !(drive >= 'a' && drive <= 'z') && !(drive >= 'A' && drive <= 'Z') { + return false + } + + if s[1] != ':' { + return false + } + + if s[2] != '\\' && s[2] != '/' { + return false + } + + return true +} + // Parse extracts repository location information from the string s. If s // starts with a backend name followed by a colon, that backend's Parse() // function is called. Otherwise, the local backend is used which interprets s @@ -56,7 +87,11 @@ func Parse(s string) (u Location, err error) { return u, nil } - // try again, with the local parser and the prefix "local:" + // if s is not a path or contains ":", it's ambiguous + if !isPath(s) && strings.ContainsRune(s, ':') { + return Location{}, errors.New("invalid backend\nIf the repo is in a local directory, you need to add a `local:` prefix") + } + u.Scheme = "local" u.Config, err = local.ParseConfig("local:" + s) if err != nil { diff --git a/src/restic/backend/location/location_test.go b/src/restic/backend/location/location_test.go index 4aaa440cd..9616d2ad5 100644 --- a/src/restic/backend/location/location_test.go +++ b/src/restic/backend/location/location_test.go @@ -58,6 +58,14 @@ var parseTests = []struct { }, }, }, + { + "/dir1/dir2", + Location{Scheme: "local", + Config: local.Config{ + Path: "/dir1/dir2", + }, + }, + }, { "local:../dir1/dir2", Location{Scheme: "local", @@ -74,7 +82,46 @@ var parseTests = []struct { }, }, }, - + { + "/dir1:foobar/dir2", + Location{Scheme: "local", + Config: local.Config{ + Path: "/dir1:foobar/dir2", + }, + }, + }, + { + `\dir1\foobar\dir2`, + Location{Scheme: "local", + Config: local.Config{ + Path: `\dir1\foobar\dir2`, + }, + }, + }, + { + `c:\dir1\foobar\dir2`, + Location{Scheme: "local", + Config: local.Config{ + Path: `c:\dir1\foobar\dir2`, + }, + }, + }, + { + `C:\Users\appveyor\AppData\Local\Temp\1\restic-test-879453535\repo`, + Location{Scheme: "local", + Config: local.Config{ + Path: `C:\Users\appveyor\AppData\Local\Temp\1\restic-test-879453535\repo`, + }, + }, + }, + { + `c:/dir1/foobar/dir2`, + Location{Scheme: "local", + Config: local.Config{ + Path: `c:/dir1/foobar/dir2`, + }, + }, + }, { "sftp:user@host:/srv/repo", Location{Scheme: "sftp", @@ -274,3 +321,19 @@ func TestParse(t *testing.T) { }) } } + +func TestInvalidScheme(t *testing.T) { + var invalidSchemes = []string{ + "foobar:xxx", + "foobar:/dir/dir2", + } + + for _, s := range invalidSchemes { + t.Run(s, func(t *testing.T) { + _, err := Parse(s) + if err == nil { + t.Fatalf("error for invalid location %q not found", s) + } + }) + } +}