github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/fs/fspath/path.go (about)

     1  // Package fspath contains routines for fspath manipulation
     2  package fspath
     3  
     4  import (
     5  	"errors"
     6  	"path"
     7  	"path/filepath"
     8  	"regexp"
     9  	"strings"
    10  
    11  	"github.com/rclone/rclone/fs/driveletter"
    12  )
    13  
    14  const (
    15  	configNameRe = `[\w_ -]+`
    16  	remoteNameRe = `^(:?` + configNameRe + `):`
    17  )
    18  
    19  var (
    20  	errInvalidCharacters = errors.New("config name contains invalid characters - may only contain 0-9, A-Z ,a-z ,_ , - and space ")
    21  	errCantBeEmpty       = errors.New("can't use empty string as a path")
    22  
    23  	// urlMatcher is a pattern to match an rclone URL
    24  	// note that this matches invalid remoteNames
    25  	urlMatcher = regexp.MustCompile(`^(:?[^\\/:]*):(.*)$`)
    26  
    27  	// configNameMatcher is a pattern to match an rclone config name
    28  	configNameMatcher = regexp.MustCompile(`^` + configNameRe + `$`)
    29  
    30  	// remoteNameMatcher is a pattern to match an rclone remote name
    31  	remoteNameMatcher = regexp.MustCompile(remoteNameRe + `$`)
    32  )
    33  
    34  // CheckConfigName returns an error if configName is invalid
    35  func CheckConfigName(configName string) error {
    36  	if !configNameMatcher.MatchString(configName) {
    37  		return errInvalidCharacters
    38  	}
    39  	return nil
    40  }
    41  
    42  // CheckRemoteName returns an error if remoteName is invalid
    43  func CheckRemoteName(remoteName string) error {
    44  	if !remoteNameMatcher.MatchString(remoteName) {
    45  		return errInvalidCharacters
    46  	}
    47  	return nil
    48  }
    49  
    50  // Parse deconstructs a remote path into configName and fsPath
    51  //
    52  // If the path is a local path then configName will be returned as "".
    53  //
    54  // So "remote:path/to/dir" will return "remote", "path/to/dir"
    55  // and "/path/to/local" will return ("", "/path/to/local")
    56  //
    57  // Note that this will turn \ into / in the fsPath on Windows
    58  //
    59  // An error may be returned if the remote name has invalid characters
    60  // in it or if the path is empty.
    61  func Parse(path string) (configName, fsPath string, err error) {
    62  	if path == "" {
    63  		return "", "", errCantBeEmpty
    64  	}
    65  	parts := urlMatcher.FindStringSubmatch(path)
    66  	configName, fsPath = "", path
    67  	if parts != nil && !driveletter.IsDriveLetter(parts[1]) {
    68  		configName, fsPath = parts[1], parts[2]
    69  		err = CheckRemoteName(configName + ":")
    70  		if err != nil {
    71  			return configName, fsPath, errInvalidCharacters
    72  		}
    73  	}
    74  	// change native directory separators to / if there are any
    75  	fsPath = filepath.ToSlash(fsPath)
    76  	return configName, fsPath, nil
    77  }
    78  
    79  // Split splits a remote into a parent and a leaf
    80  //
    81  // if it returns leaf as an empty string then remote is a directory
    82  //
    83  // if it returns parent as an empty string then that means the current directory
    84  //
    85  // The returned values have the property that parent + leaf == remote
    86  // (except under Windows where \ will be translated into /)
    87  func Split(remote string) (parent string, leaf string, err error) {
    88  	remoteName, remotePath, err := Parse(remote)
    89  	if err != nil {
    90  		return "", "", err
    91  	}
    92  	if remoteName != "" {
    93  		remoteName += ":"
    94  	}
    95  	// Construct new remote name without last segment
    96  	parent, leaf = path.Split(remotePath)
    97  	return remoteName + parent, leaf, nil
    98  }
    99  
   100  // JoinRootPath joins any number of path elements into a single path, adding a
   101  // separating slash if necessary. The result is Cleaned; in particular,
   102  // all empty strings are ignored.
   103  // If the first non empty element has a leading "//" this is preserved.
   104  func JoinRootPath(elem ...string) string {
   105  	for i, e := range elem {
   106  		if e != "" {
   107  			if strings.HasPrefix(e, "//") {
   108  				return "/" + path.Clean(strings.Join(elem[i:], "/"))
   109  			}
   110  			return path.Clean(strings.Join(elem[i:], "/"))
   111  		}
   112  	}
   113  	return ""
   114  }