github.com/kubesphere/s2irun@v3.2.1+incompatible/pkg/scm/git/url.go (about)

     1  package git
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"path/filepath"
     7  	"regexp"
     8  	"runtime"
     9  	"strings"
    10  )
    11  
    12  // According to git-clone(1), a "Git URL" can be in one of three broad types:
    13  // 1) A standards-compliant URL.
    14  //    a) The scheme may be followed by '://',
    15  //       e.g. https://github.com/openshift/origin, file:///foo/bar, etc.  In
    16  //       this case, note that among other things, a standards-compliant file URL
    17  //       must have an empty host part, an absolute path and no backslashes.  The
    18  //       Git for Windows URL parser accepts many non-compliant URLs, but we
    19  //       don't.
    20  //    b) Alternatively, the scheme may be followed by '::', in which case it is
    21  //       treated as an transport/opaque address pair, e.g.
    22  //       http::http://github.com/openshift/origin.git .
    23  // 2) The "alternative scp-like syntax", including a ':' with no preceding '/',
    24  //    but not of the form C:... on Windows, e.g.
    25  //    git@github.com:openshift/origin, etc.
    26  // 3) An OS-specific relative or absolute local file path, e.g. foo/bar,
    27  //    C:\foo\bar, etc.
    28  //
    29  // We extend all of the above URL types to additionally accept an optional
    30  // appended #fragment, which is given to specify a git reference.
    31  //
    32  // The git client allows Git URL rewriting rules to be defined.  The meaning of
    33  // a Git URL cannot be 100% guaranteed without consulting the rewriting rules.
    34  
    35  // URLType indicates the type of the URL (see above)
    36  type URLType int
    37  
    38  const (
    39  	// URLTypeURL is the URL type (see above)
    40  	URLTypeURL URLType = iota
    41  	// URLTypeSCP is the SCP type (see above)
    42  	URLTypeSCP
    43  	// URLTypeLocal is the local type (see above)
    44  	URLTypeLocal
    45  	// URLTypeBinary is the URL to download file
    46  	URLTypeBinary
    47  )
    48  
    49  // String returns a string representation of the URLType
    50  func (t URLType) String() string {
    51  	switch t {
    52  	case URLTypeURL:
    53  		return "URLTypeURL"
    54  	case URLTypeSCP:
    55  		return "URLTypeSCP"
    56  	case URLTypeLocal:
    57  		return "URLTypeLocal"
    58  	case URLTypeBinary:
    59  		return "URLTypeBinary"
    60  	}
    61  	panic("unknown URLType")
    62  }
    63  
    64  // GoString returns a Go string representation of the URLType
    65  func (t URLType) GoString() string {
    66  	return t.String()
    67  }
    68  
    69  // URL represents a "Git URL"
    70  type URL struct {
    71  	URL  url.URL
    72  	Type URLType
    73  }
    74  
    75  var urlSchemeRegexp = regexp.MustCompile("(?i)^[a-z][-a-z0-9+.]*:") // matches scheme: according to RFC3986
    76  var dosDriveRegexp = regexp.MustCompile("(?i)^[a-z]:")
    77  var scpRegexp = regexp.MustCompile("^" +
    78  	"(?:([^@/]*)@)?" + // user@ (optional)
    79  	"([^/]*):" + //            host:
    80  	"(.*)" + //                     path
    81  	"$")
    82  
    83  func splitOnByte(s string, c byte) (string, string) {
    84  	if i := strings.IndexByte(s, c); i != -1 {
    85  		return s[:i], s[i+1:]
    86  	}
    87  	return s, ""
    88  }
    89  
    90  // Parse parses a "Git URL"
    91  func Parse(rawurl string, isBinaryURL bool) (*URL, error) {
    92  	if urlSchemeRegexp.MatchString(rawurl) &&
    93  		(runtime.GOOS != "windows" || !dosDriveRegexp.MatchString(rawurl)) {
    94  		u, err := url.Parse(rawurl)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  		if u.Scheme == "file" && u.Opaque == "" {
    99  			if u.Host != "" {
   100  				return nil, fmt.Errorf("file url %q has non-empty host %q", rawurl, u.Host)
   101  			}
   102  			if runtime.GOOS == "windows" && (len(u.Path) == 0 || !filepath.IsAbs(u.Path[1:])) {
   103  				return nil, fmt.Errorf("file url %q has non-absolute path %q", rawurl, u.Path)
   104  			}
   105  		}
   106  		if isBinaryURL {
   107  			return &URL{
   108  				URL:  *u,
   109  				Type: URLTypeBinary,
   110  			}, nil
   111  		} else {
   112  			return &URL{
   113  				URL:  *u,
   114  				Type: URLTypeURL,
   115  			}, nil
   116  		}
   117  	}
   118  
   119  	s, fragment := splitOnByte(rawurl, '#')
   120  
   121  	if m := scpRegexp.FindStringSubmatch(s); m != nil &&
   122  		(runtime.GOOS != "windows" || !dosDriveRegexp.MatchString(s)) {
   123  		u := &url.URL{
   124  			Host:     m[2],
   125  			Path:     m[3],
   126  			Fragment: fragment,
   127  		}
   128  		if m[1] != "" {
   129  			u.User = url.User(m[1])
   130  		}
   131  
   132  		return &URL{
   133  			URL:  *u,
   134  			Type: URLTypeSCP,
   135  		}, nil
   136  	}
   137  
   138  	return &URL{
   139  		URL: url.URL{
   140  			Path:     s,
   141  			Fragment: fragment,
   142  		},
   143  		Type: URLTypeLocal,
   144  	}, nil
   145  }
   146  
   147  // MustParse parses a "Git URL" and panics on failure
   148  func MustParse(rawurl string) *URL {
   149  	u, err := Parse(rawurl, false)
   150  	if err != nil {
   151  		panic(err)
   152  	}
   153  	return u
   154  }
   155  
   156  // String returns a string representation of the URL
   157  func (u URL) String() string {
   158  	var s string
   159  	switch u.Type {
   160  	case URLTypeURL:
   161  		return u.URL.String()
   162  	case URLTypeBinary:
   163  		return u.URL.String()
   164  	case URLTypeSCP:
   165  		if u.URL.User != nil {
   166  			s = u.URL.User.Username() + "@"
   167  		}
   168  		s += u.URL.Host + ":" + u.URL.Path
   169  	case URLTypeLocal:
   170  		s = u.URL.Path
   171  	}
   172  	if u.URL.RawQuery != "" {
   173  		s += "?" + u.URL.RawQuery
   174  	}
   175  	if u.URL.Fragment != "" {
   176  		s += "#" + u.URL.Fragment
   177  	}
   178  	return s
   179  }
   180  
   181  // StringNoFragment returns a string representation of the URL without its
   182  // fragment
   183  func (u URL) StringNoFragment() string {
   184  	u.URL.Fragment = ""
   185  	return u.String()
   186  }
   187  
   188  // IsLocal returns true if the Git URL refers to a local repository
   189  func (u URL) IsLocal() bool {
   190  	return u.Type == URLTypeLocal || (u.Type == URLTypeURL && u.URL.Scheme == "file" && u.URL.Opaque == "")
   191  }
   192  
   193  // LocalPath returns the path to a local repository in OS-native format.  It is
   194  // assumed that IsLocal() is true
   195  func (u URL) LocalPath() string {
   196  	switch {
   197  	case u.Type == URLTypeLocal:
   198  		return u.URL.Path
   199  	case u.Type == URLTypeURL && u.URL.Scheme == "file" && u.URL.Opaque == "":
   200  		if runtime.GOOS == "windows" && len(u.URL.Path) > 0 && u.URL.Path[0] == '/' {
   201  			return filepath.FromSlash(u.URL.Path[1:])
   202  		}
   203  		return filepath.FromSlash(u.URL.Path)
   204  	}
   205  	panic("LocalPath called on non-local URL")
   206  }