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 }