github.com/nak3/source-to-image@v1.1.10-0.20180319140719-2ed55639898d/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  )
    46  
    47  // String returns a string representation of the URLType
    48  func (t URLType) String() string {
    49  	switch t {
    50  	case URLTypeURL:
    51  		return "URLTypeURL"
    52  	case URLTypeSCP:
    53  		return "URLTypeSCP"
    54  	case URLTypeLocal:
    55  		return "URLTypeLocal"
    56  	}
    57  	panic("unknown URLType")
    58  }
    59  
    60  // GoString returns a Go string representation of the URLType
    61  func (t URLType) GoString() string {
    62  	return t.String()
    63  }
    64  
    65  // URL represents a "Git URL"
    66  type URL struct {
    67  	URL  url.URL
    68  	Type URLType
    69  }
    70  
    71  var urlSchemeRegexp = regexp.MustCompile("(?i)^[a-z][-a-z0-9+.]*:") // matches scheme: according to RFC3986
    72  var dosDriveRegexp = regexp.MustCompile("(?i)^[a-z]:")
    73  var scpRegexp = regexp.MustCompile("^" +
    74  	"(?:([^@/]*)@)?" + // user@ (optional)
    75  	"([^/]*):" + //            host:
    76  	"(.*)" + //                     path
    77  	"$")
    78  
    79  func splitOnByte(s string, c byte) (string, string) {
    80  	if i := strings.IndexByte(s, c); i != -1 {
    81  		return s[:i], s[i+1:]
    82  	}
    83  	return s, ""
    84  }
    85  
    86  // Parse parses a "Git URL"
    87  func Parse(rawurl string) (*URL, error) {
    88  	if urlSchemeRegexp.MatchString(rawurl) &&
    89  		(runtime.GOOS != "windows" || !dosDriveRegexp.MatchString(rawurl)) {
    90  		u, err := url.Parse(rawurl)
    91  		if err != nil {
    92  			return nil, err
    93  		}
    94  		if u.Scheme == "file" && u.Opaque == "" {
    95  			if u.Host != "" {
    96  				return nil, fmt.Errorf("file url %q has non-empty host %q", rawurl, u.Host)
    97  			}
    98  			if runtime.GOOS == "windows" && (len(u.Path) == 0 || !filepath.IsAbs(u.Path[1:])) {
    99  				return nil, fmt.Errorf("file url %q has non-absolute path %q", rawurl, u.Path)
   100  			}
   101  		}
   102  
   103  		return &URL{
   104  			URL:  *u,
   105  			Type: URLTypeURL,
   106  		}, nil
   107  	}
   108  
   109  	s, fragment := splitOnByte(rawurl, '#')
   110  
   111  	if m := scpRegexp.FindStringSubmatch(s); m != nil &&
   112  		(runtime.GOOS != "windows" || !dosDriveRegexp.MatchString(s)) {
   113  		u := &url.URL{
   114  			Host:     m[2],
   115  			Path:     m[3],
   116  			Fragment: fragment,
   117  		}
   118  		if m[1] != "" {
   119  			u.User = url.User(m[1])
   120  		}
   121  
   122  		return &URL{
   123  			URL:  *u,
   124  			Type: URLTypeSCP,
   125  		}, nil
   126  	}
   127  
   128  	return &URL{
   129  		URL: url.URL{
   130  			Path:     s,
   131  			Fragment: fragment,
   132  		},
   133  		Type: URLTypeLocal,
   134  	}, nil
   135  }
   136  
   137  // MustParse parses a "Git URL" and panics on failure
   138  func MustParse(rawurl string) *URL {
   139  	u, err := Parse(rawurl)
   140  	if err != nil {
   141  		panic(err)
   142  	}
   143  	return u
   144  }
   145  
   146  // String returns a string representation of the URL
   147  func (u URL) String() string {
   148  	var s string
   149  	switch u.Type {
   150  	case URLTypeURL:
   151  		return u.URL.String()
   152  	case URLTypeSCP:
   153  		if u.URL.User != nil {
   154  			s = u.URL.User.Username() + "@"
   155  		}
   156  		s += u.URL.Host + ":" + u.URL.Path
   157  	case URLTypeLocal:
   158  		s = u.URL.Path
   159  	}
   160  	if u.URL.RawQuery != "" {
   161  		s += "?" + u.URL.RawQuery
   162  	}
   163  	if u.URL.Fragment != "" {
   164  		s += "#" + u.URL.Fragment
   165  	}
   166  	return s
   167  }
   168  
   169  // StringNoFragment returns a string representation of the URL without its
   170  // fragment
   171  func (u URL) StringNoFragment() string {
   172  	u.URL.Fragment = ""
   173  	return u.String()
   174  }
   175  
   176  // IsLocal returns true if the Git URL refers to a local repository
   177  func (u URL) IsLocal() bool {
   178  	return u.Type == URLTypeLocal || (u.Type == URLTypeURL && u.URL.Scheme == "file" && u.URL.Opaque == "")
   179  }
   180  
   181  // LocalPath returns the path to a local repository in OS-native format.  It is
   182  // assumed that IsLocal() is true
   183  func (u URL) LocalPath() string {
   184  	switch {
   185  	case u.Type == URLTypeLocal:
   186  		return u.URL.Path
   187  	case u.Type == URLTypeURL && u.URL.Scheme == "file" && u.URL.Opaque == "":
   188  		if runtime.GOOS == "windows" && len(u.URL.Path) > 0 && u.URL.Path[0] == '/' {
   189  			return filepath.FromSlash(u.URL.Path[1:])
   190  		}
   191  		return filepath.FromSlash(u.URL.Path)
   192  	}
   193  	panic("LocalPath called on non-local URL")
   194  }