2015-12-28 14:57:20 +00:00
|
|
|
// Package location implements parsing the restic repository location from a string.
|
|
|
|
package location
|
2015-12-28 14:51:24 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
|
2017-07-08 13:38:48 +00:00
|
|
|
"github.com/restic/restic/internal/backend/azure"
|
2017-07-23 12:21:03 +00:00
|
|
|
"github.com/restic/restic/internal/backend/b2"
|
2017-07-08 13:34:23 +00:00
|
|
|
"github.com/restic/restic/internal/backend/gs"
|
2017-07-23 12:21:03 +00:00
|
|
|
"github.com/restic/restic/internal/backend/local"
|
2018-03-13 21:30:51 +00:00
|
|
|
"github.com/restic/restic/internal/backend/rclone"
|
2017-07-23 12:21:03 +00:00
|
|
|
"github.com/restic/restic/internal/backend/rest"
|
|
|
|
"github.com/restic/restic/internal/backend/s3"
|
|
|
|
"github.com/restic/restic/internal/backend/sftp"
|
|
|
|
"github.com/restic/restic/internal/backend/swift"
|
|
|
|
"github.com/restic/restic/internal/errors"
|
2015-12-28 14:51:24 +00:00
|
|
|
)
|
|
|
|
|
2015-12-28 14:57:20 +00:00
|
|
|
// Location specifies the location of a repository, including the method of
|
|
|
|
// access and (possibly) credentials needed for access.
|
|
|
|
type Location struct {
|
2015-12-28 14:51:24 +00:00
|
|
|
Scheme string
|
|
|
|
Config interface{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type parser struct {
|
2020-03-20 22:52:27 +00:00
|
|
|
scheme string
|
|
|
|
parse func(string) (interface{}, error)
|
|
|
|
stripPassword func(string) string
|
2015-12-28 14:51:24 +00:00
|
|
|
}
|
|
|
|
|
2023-04-21 19:35:34 +00:00
|
|
|
func configToAny[C any](parser func(string) (*C, error)) func(string) (interface{}, error) {
|
2023-04-20 20:40:21 +00:00
|
|
|
return func(s string) (interface{}, error) {
|
|
|
|
return parser(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-28 14:51:24 +00:00
|
|
|
// parsers is a list of valid config parsers for the backends. The first parser
|
|
|
|
// is the fallback and should always be set to the local backend.
|
|
|
|
var parsers = []parser{
|
2023-04-20 20:40:21 +00:00
|
|
|
{"b2", configToAny(b2.ParseConfig), noPassword},
|
|
|
|
{"local", configToAny(local.ParseConfig), noPassword},
|
|
|
|
{"sftp", configToAny(sftp.ParseConfig), noPassword},
|
|
|
|
{"s3", configToAny(s3.ParseConfig), noPassword},
|
|
|
|
{"gs", configToAny(gs.ParseConfig), noPassword},
|
|
|
|
{"azure", configToAny(azure.ParseConfig), noPassword},
|
|
|
|
{"swift", configToAny(swift.ParseConfig), noPassword},
|
|
|
|
{"rest", configToAny(rest.ParseConfig), rest.StripPassword},
|
|
|
|
{"rclone", configToAny(rclone.ParseConfig), noPassword},
|
2020-03-20 22:52:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// noPassword returns the repository location unchanged (there's no sensitive information there)
|
|
|
|
func noPassword(s string) string {
|
|
|
|
return s
|
2015-12-28 14:51:24 +00:00
|
|
|
}
|
|
|
|
|
2017-06-30 18:40:27 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-12-28 15:42:44 +00:00
|
|
|
// 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
|
|
|
|
// as the name of a directory.
|
|
|
|
func Parse(s string) (u Location, err error) {
|
2015-12-28 14:51:24 +00:00
|
|
|
scheme := extractScheme(s)
|
|
|
|
u.Scheme = scheme
|
|
|
|
|
|
|
|
for _, parser := range parsers {
|
|
|
|
if parser.scheme != scheme {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
u.Config, err = parser.parse(s)
|
|
|
|
if err != nil {
|
2015-12-28 14:57:20 +00:00
|
|
|
return Location{}, err
|
2015-12-28 14:51:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return u, nil
|
|
|
|
}
|
|
|
|
|
2017-06-30 18:40:27 +00:00
|
|
|
// if s is not a path or contains ":", it's ambiguous
|
|
|
|
if !isPath(s) && strings.ContainsRune(s, ':') {
|
2022-05-07 20:23:59 +00:00
|
|
|
return Location{}, errors.New("invalid backend\nIf the repository is in a local directory, you need to add a `local:` prefix")
|
2017-06-30 18:40:27 +00:00
|
|
|
}
|
|
|
|
|
2015-12-28 14:51:24 +00:00
|
|
|
u.Scheme = "local"
|
|
|
|
u.Config, err = local.ParseConfig("local:" + s)
|
|
|
|
if err != nil {
|
2015-12-28 14:57:20 +00:00
|
|
|
return Location{}, err
|
2015-12-28 14:51:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return u, nil
|
|
|
|
}
|
|
|
|
|
2020-03-20 22:52:27 +00:00
|
|
|
// StripPassword returns a displayable version of a repository location (with any sensitive information removed)
|
|
|
|
func StripPassword(s string) string {
|
|
|
|
scheme := extractScheme(s)
|
|
|
|
|
|
|
|
for _, parser := range parsers {
|
|
|
|
if parser.scheme != scheme {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return parser.stripPassword(s)
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2015-12-28 14:51:24 +00:00
|
|
|
func extractScheme(s string) string {
|
2022-11-27 17:09:59 +00:00
|
|
|
scheme, _, _ := strings.Cut(s, ":")
|
|
|
|
return scheme
|
2015-12-28 14:51:24 +00:00
|
|
|
}
|