120 lines
2.2 KiB
Go
120 lines
2.2 KiB
Go
// Copyright (C) 2024 Umorpha Systems
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package gitcache
|
|
|
|
import (
|
|
_url "net/url"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// reIsURL mimics git's url.c:is_url()
|
|
var reIsURL = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9+.-]*://`)
|
|
|
|
// NormalizeURL parses a URL in accordance with the "GIT URLS"
|
|
// section of git-fetch(1), and normalizes it.
|
|
func NormalizeURL(url string) (string, bool) {
|
|
switch {
|
|
case reIsURL.MatchString(url):
|
|
u, err := _url.Parse(url)
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
|
|
// work this left-to-right
|
|
|
|
// 1. scheme
|
|
switch u.Scheme {
|
|
case "ssh",
|
|
"git",
|
|
"http", "https",
|
|
"ftp", "ftps",
|
|
"file":
|
|
// OK
|
|
default:
|
|
return "", false
|
|
}
|
|
|
|
// 2. userinfo
|
|
if u.User != nil {
|
|
if u.Scheme == "ssh" {
|
|
u.User = _url.User(u.User.Username())
|
|
} else {
|
|
u.User = nil
|
|
}
|
|
}
|
|
|
|
// 3. host[:port]
|
|
if u.Host == "" && u.Scheme != "file" {
|
|
return "", false
|
|
}
|
|
u.Host = strings.ToLower(u.Host)
|
|
|
|
// 4. path
|
|
u.Path = path.Clean(u.Path)
|
|
switch u.Path {
|
|
case ".", "/":
|
|
u.Path = ""
|
|
}
|
|
if u.Host == "" && u.Path == "" {
|
|
u.Path = "/"
|
|
}
|
|
|
|
// 5. query
|
|
switch u.Scheme {
|
|
case "http", "https":
|
|
// > Clients MUST strip a trailing `/`, if present, from the
|
|
// > user supplied `$GIT_URL` string
|
|
// -- gitprotocol-http(5)
|
|
//
|
|
// ... and that might include the query
|
|
u.RawQuery = strings.TrimRight(u.RawQuery, "/")
|
|
}
|
|
|
|
// 6. fragment
|
|
u.Fragment = ""
|
|
|
|
return u.String(), true
|
|
case filepath.IsAbs(url):
|
|
u := &_url.URL{
|
|
Scheme: "file",
|
|
Path: filepath.ToSlash(url),
|
|
}
|
|
url, ok := NormalizeURL(u.String())
|
|
if !ok {
|
|
panic("should not happen")
|
|
}
|
|
return url, true
|
|
default: // scp-like
|
|
uauth, upath, ok := strings.Cut(url, ":")
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
if strings.Contains(uauth, "/") {
|
|
return "", false
|
|
}
|
|
if !path.IsAbs(upath) {
|
|
if strings.HasPrefix(upath, "~") {
|
|
upath = "/" + upath
|
|
} else {
|
|
upath = "/~/" + upath
|
|
}
|
|
}
|
|
u := &_url.URL{
|
|
Scheme: "ssh",
|
|
Path: path.Clean(upath),
|
|
}
|
|
atIdx := strings.LastIndex(uauth, "@")
|
|
if atIdx < 0 {
|
|
u.Host = uauth
|
|
} else {
|
|
u.User = _url.User(uauth[:atIdx])
|
|
u.Host = uauth[atIdx+1:]
|
|
}
|
|
return NormalizeURL(u.String())
|
|
}
|
|
}
|