diff --git a/internal/backend/azure/config.go b/internal/backend/azure/config.go index cc5169e3e..55b26d4f1 100644 --- a/internal/backend/azure/config.go +++ b/internal/backend/azure/config.go @@ -43,14 +43,13 @@ func ParseConfig(s string) (interface{}, error) { // use the first entry of the path as the bucket name and the // remainder as prefix - data := strings.SplitN(s, ":", 2) - if len(data) < 2 { + container, prefix, colon := strings.Cut(s, ":") + if !colon { return nil, errors.New("azure: invalid format: bucket name or path not found") } - container, path := data[0], path.Clean(data[1]) - path = strings.TrimPrefix(path, "/") + prefix = strings.TrimPrefix(path.Clean(prefix), "/") cfg := NewConfig() cfg.Container = container - cfg.Prefix = path + cfg.Prefix = prefix return cfg, nil } diff --git a/internal/backend/b2/config.go b/internal/backend/b2/config.go index 98e8e1445..ba5141834 100644 --- a/internal/backend/b2/config.go +++ b/internal/backend/b2/config.go @@ -37,7 +37,7 @@ var bucketName = regexp.MustCompile("^[a-zA-Z0-9-]+$") // https://help.backblaze.com/hc/en-us/articles/217666908-What-you-need-to-know-about-B2-Bucket-names func checkBucketName(name string) error { if name == "" { - return errors.New("bucket name is empty") + return errors.New("bucket name not found") } if len(name) < 6 { @@ -64,30 +64,18 @@ func ParseConfig(s string) (interface{}, error) { } s = s[3:] - data := strings.SplitN(s, ":", 2) - if len(data) == 0 || len(data[0]) == 0 { - return nil, errors.New("bucket name not found") - } - - cfg := NewConfig() - cfg.Bucket = data[0] - - if err := checkBucketName(cfg.Bucket); err != nil { + bucket, prefix, _ := strings.Cut(s, ":") + if err := checkBucketName(bucket); err != nil { return nil, err } - if len(data) == 2 { - p := data[1] - if len(p) > 0 { - p = path.Clean(p) - } - - if len(p) > 0 && path.IsAbs(p) { - p = p[1:] - } - - cfg.Prefix = p + if len(prefix) > 0 { + prefix = strings.TrimPrefix(path.Clean(prefix), "/") } + cfg := NewConfig() + cfg.Bucket = bucket + cfg.Prefix = prefix + return cfg, nil } diff --git a/internal/backend/gs/config.go b/internal/backend/gs/config.go index bd152d775..33aec4c99 100644 --- a/internal/backend/gs/config.go +++ b/internal/backend/gs/config.go @@ -42,17 +42,15 @@ func ParseConfig(s string) (interface{}, error) { // use the first entry of the path as the bucket name and the // remainder as prefix - data := strings.SplitN(s, ":", 2) - if len(data) < 2 { + bucket, prefix, colon := strings.Cut(s, ":") + if !colon { return nil, errors.New("gs: invalid format: bucket name or path not found") } - bucket, path := data[0], path.Clean(data[1]) - - path = strings.TrimPrefix(path, "/") + prefix = strings.TrimPrefix(path.Clean(prefix), "/") cfg := NewConfig() cfg.Bucket = bucket - cfg.Prefix = path + cfg.Prefix = prefix return cfg, nil } diff --git a/internal/backend/location/location.go b/internal/backend/location/location.go index daef51d40..a732233cc 100644 --- a/internal/backend/location/location.go +++ b/internal/backend/location/location.go @@ -127,6 +127,6 @@ func StripPassword(s string) string { } func extractScheme(s string) string { - data := strings.SplitN(s, ":", 2) - return data[0] + scheme, _, _ := strings.Cut(s, ":") + return scheme } diff --git a/internal/backend/s3/config.go b/internal/backend/s3/config.go index 9e83f4004..775580450 100644 --- a/internal/backend/s3/config.go +++ b/internal/backend/s3/config.go @@ -59,8 +59,8 @@ func ParseConfig(s string) (interface{}, error) { return nil, errors.New("s3: bucket name not found") } - path := strings.SplitN(url.Path[1:], "/", 2) - return createConfig(url.Host, path, url.Scheme == "http") + bucket, path, _ := strings.Cut(url.Path[1:], "/") + return createConfig(url.Host, bucket, path, url.Scheme == "http") case strings.HasPrefix(s, "s3://"): s = s[5:] case strings.HasPrefix(s, "s3:"): @@ -70,24 +70,24 @@ func ParseConfig(s string) (interface{}, error) { } // use the first entry of the path as the endpoint and the // remainder as bucket name and prefix - path := strings.SplitN(s, "/", 3) - return createConfig(path[0], path[1:], false) + endpoint, rest, _ := strings.Cut(s, "/") + bucket, prefix, _ := strings.Cut(rest, "/") + return createConfig(endpoint, bucket, prefix, false) } -func createConfig(endpoint string, p []string, useHTTP bool) (interface{}, error) { - if len(p) < 1 { +func createConfig(endpoint, bucket, prefix string, useHTTP bool) (interface{}, error) { + if endpoint == "" { return nil, errors.New("s3: invalid format, host/region or bucket name not found") } - var prefix string - if len(p) > 1 && p[1] != "" { - prefix = path.Clean(p[1]) + if prefix != "" { + prefix = path.Clean(prefix) } cfg := NewConfig() cfg.Endpoint = endpoint cfg.UseHTTP = useHTTP - cfg.Bucket = p[0] + cfg.Bucket = bucket cfg.Prefix = prefix return cfg, nil } diff --git a/internal/backend/s3/config_test.go b/internal/backend/s3/config_test.go index 77a31fda3..821fbc244 100644 --- a/internal/backend/s3/config_test.go +++ b/internal/backend/s3/config_test.go @@ -1,6 +1,9 @@ package s3 -import "testing" +import ( + "strings" + "testing" +) var configTests = []struct { s string @@ -111,3 +114,14 @@ func TestParseConfig(t *testing.T) { } } } + +func TestParseError(t *testing.T) { + const prefix = "s3: invalid format," + + for _, s := range []string{"", "/", "//", "/bucket/prefix"} { + _, err := ParseConfig("s3://" + s) + if err == nil || !strings.HasPrefix(err.Error(), prefix) { + t.Errorf("expected %q, got %q", prefix, err) + } + } +} diff --git a/internal/backend/sftp/config.go b/internal/backend/sftp/config.go index ec38ee467..2f3b3697c 100644 --- a/internal/backend/sftp/config.go +++ b/internal/backend/sftp/config.go @@ -60,14 +60,13 @@ func ParseConfig(s string) (interface{}, error) { // "user@host:path" in s s = s[5:] // split user@host and path at the colon - data := strings.SplitN(s, ":", 2) - if len(data) < 2 { + var colon bool + host, dir, colon = strings.Cut(s, ":") + if !colon { return nil, errors.New("sftp: invalid format, hostname or path not found") } - host = data[0] - dir = data[1] // split user and host at the "@" - data = strings.SplitN(host, "@", 3) + data := strings.SplitN(host, "@", 3) if len(data) == 3 { user = data[0] + "@" + data[1] host = data[2] diff --git a/internal/backend/swift/config.go b/internal/backend/swift/config.go index d2751dd1a..ced256752 100644 --- a/internal/backend/swift/config.go +++ b/internal/backend/swift/config.go @@ -51,17 +51,13 @@ func NewConfig() Config { // ParseConfig parses the string s and extract swift's container name and prefix. func ParseConfig(s string) (interface{}, error) { - data := strings.SplitN(s, ":", 3) - if len(data) != 3 { + if !strings.HasPrefix(s, "swift:") { return nil, errors.New("invalid URL, expected: swift:container-name:/[prefix]") } + s = strings.TrimPrefix(s, "swift:") - scheme, container, prefix := data[0], data[1], data[2] - if scheme != "swift" { - return nil, errors.Errorf("unexpected prefix: %s", data[0]) - } - - if len(prefix) == 0 { + container, prefix, _ := strings.Cut(s, ":") + if prefix == "" { return nil, errors.Errorf("prefix is empty") } diff --git a/internal/options/options.go b/internal/options/options.go index 43d9b63da..7490ac430 100644 --- a/internal/options/options.go +++ b/internal/options/options.go @@ -92,14 +92,10 @@ func (h helpList) Swap(i, j int) { // splitKeyValue splits at the first equals (=) sign. func splitKeyValue(s string) (key string, value string) { - data := strings.SplitN(s, "=", 2) - key = strings.ToLower(strings.TrimSpace(data[0])) - if len(data) == 1 { - // no equals sign is treated as the empty value - return key, "" - } - - return key, strings.TrimSpace(data[1]) + key, value, _ = strings.Cut(s, "=") + key = strings.ToLower(strings.TrimSpace(key)) + value = strings.TrimSpace(value) + return key, value } // Parse takes a slice of key=value pairs and returns an Options type.