github.com/mckael/restic@v0.8.3/internal/backend/location/location.go (about)

     1  // Package location implements parsing the restic repository location from a string.
     2  package location
     3  
     4  import (
     5  	"strings"
     6  
     7  	"github.com/restic/restic/internal/backend/azure"
     8  	"github.com/restic/restic/internal/backend/b2"
     9  	"github.com/restic/restic/internal/backend/gs"
    10  	"github.com/restic/restic/internal/backend/local"
    11  	"github.com/restic/restic/internal/backend/rest"
    12  	"github.com/restic/restic/internal/backend/s3"
    13  	"github.com/restic/restic/internal/backend/sftp"
    14  	"github.com/restic/restic/internal/backend/swift"
    15  	"github.com/restic/restic/internal/errors"
    16  )
    17  
    18  // Location specifies the location of a repository, including the method of
    19  // access and (possibly) credentials needed for access.
    20  type Location struct {
    21  	Scheme string
    22  	Config interface{}
    23  }
    24  
    25  type parser struct {
    26  	scheme string
    27  	parse  func(string) (interface{}, error)
    28  }
    29  
    30  // parsers is a list of valid config parsers for the backends. The first parser
    31  // is the fallback and should always be set to the local backend.
    32  var parsers = []parser{
    33  	{"b2", b2.ParseConfig},
    34  	{"local", local.ParseConfig},
    35  	{"sftp", sftp.ParseConfig},
    36  	{"s3", s3.ParseConfig},
    37  	{"gs", gs.ParseConfig},
    38  	{"azure", azure.ParseConfig},
    39  	{"swift", swift.ParseConfig},
    40  	{"rest", rest.ParseConfig},
    41  }
    42  
    43  func isPath(s string) bool {
    44  	if strings.HasPrefix(s, "../") || strings.HasPrefix(s, `..\`) {
    45  		return true
    46  	}
    47  
    48  	if strings.HasPrefix(s, "/") || strings.HasPrefix(s, `\`) {
    49  		return true
    50  	}
    51  
    52  	if len(s) < 3 {
    53  		return false
    54  	}
    55  
    56  	// check for drive paths
    57  	drive := s[0]
    58  	if !(drive >= 'a' && drive <= 'z') && !(drive >= 'A' && drive <= 'Z') {
    59  		return false
    60  	}
    61  
    62  	if s[1] != ':' {
    63  		return false
    64  	}
    65  
    66  	if s[2] != '\\' && s[2] != '/' {
    67  		return false
    68  	}
    69  
    70  	return true
    71  }
    72  
    73  // Parse extracts repository location information from the string s. If s
    74  // starts with a backend name followed by a colon, that backend's Parse()
    75  // function is called. Otherwise, the local backend is used which interprets s
    76  // as the name of a directory.
    77  func Parse(s string) (u Location, err error) {
    78  	scheme := extractScheme(s)
    79  	u.Scheme = scheme
    80  
    81  	for _, parser := range parsers {
    82  		if parser.scheme != scheme {
    83  			continue
    84  		}
    85  
    86  		u.Config, err = parser.parse(s)
    87  		if err != nil {
    88  			return Location{}, err
    89  		}
    90  
    91  		return u, nil
    92  	}
    93  
    94  	// if s is not a path or contains ":", it's ambiguous
    95  	if !isPath(s) && strings.ContainsRune(s, ':') {
    96  		return Location{}, errors.New("invalid backend\nIf the repo is in a local directory, you need to add a `local:` prefix")
    97  	}
    98  
    99  	u.Scheme = "local"
   100  	u.Config, err = local.ParseConfig("local:" + s)
   101  	if err != nil {
   102  		return Location{}, err
   103  	}
   104  
   105  	return u, nil
   106  }
   107  
   108  func extractScheme(s string) string {
   109  	data := strings.SplitN(s, ":", 2)
   110  	return data[0]
   111  }