blob: 21e9a6f7c15e920a4d4a71b7b6e85c2114a57ab7 [file] [log] [blame] [edit]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package moduleaddrs
import (
"fmt"
"net/url"
"regexp"
"strings"
)
// detectGit translates Git SSH URLs into normal-shaped URLs.
func detectGit(src string) (string, bool, error) {
if len(src) == 0 {
return "", false, nil
}
u, err := detectSSH(src)
if err != nil {
return "", true, err
}
if u == nil {
return "", false, nil
}
// We require the username to be "git" to assume that this is a Git URL
if u.User.Username() != "git" {
return "", false, nil
}
return "git::" + u.String(), true, nil
}
// detectGitHub detects shorthand schemeless references to github.com and
// translates them into git HTTP source addresses.
func detectGitHub(src string) (string, bool, error) {
if len(src) == 0 {
return "", false, nil
}
if strings.HasPrefix(src, "github.com/") {
src, rawQuery, _ := strings.Cut(src, "?")
parts := strings.Split(src, "/")
if len(parts) < 3 {
return "", false, fmt.Errorf(
"GitHub URLs should be github.com/username/repo")
}
urlStr := fmt.Sprintf("https://%s", strings.Join(parts[:3], "/"))
url, err := url.Parse(urlStr)
if err != nil {
return "", true, fmt.Errorf("error parsing GitHub URL: %s", err)
}
url.RawQuery = rawQuery
if !strings.HasSuffix(url.Path, ".git") {
url.Path += ".git"
}
if len(parts) > 3 {
url.Path += "//" + strings.Join(parts[3:], "/")
}
return "git::" + url.String(), true, nil
}
return "", false, nil
}
// detectBitBucket detects shorthand schemeless references to bitbucket.org and
// translates them into git HTTP source addresses.
func detectBitBucket(src string) (string, bool, error) {
if len(src) == 0 {
return "", false, nil
}
if strings.HasPrefix(src, "bitbucket.org/") {
u, err := url.Parse("https://" + src)
if err != nil {
return "", true, fmt.Errorf("error parsing BitBucket URL: %s", err)
}
// NOTE: A long, long time ago bitbucket.org repositories could
// potentially be either Git or Mercurial repositories and we would've
// needed to make an API call here to know which to generate.
//
// Thankfully BitBucket now only supports Git, and so we can just
// assume all bitbucket.org strings are trying to refer to Git
// repositories.
if !strings.HasSuffix(u.Path, ".git") {
u.Path += ".git"
}
return "git::" + u.String(), true, nil
}
return "", false, nil
}
// sshPattern matches SCP-like SSH patterns (user@host:path)
var sshPattern = regexp.MustCompile("^(?:([^@]+)@)?([^:]+):/?(.+)$")
// detectSSH determines if the src string matches an SSH-like URL and
// converts it into a net.URL. This returns nil if the string doesn't match
// the SSH pattern.
func detectSSH(src string) (*url.URL, error) {
matched := sshPattern.FindStringSubmatch(src)
if matched == nil {
return nil, nil
}
user := matched[1]
host := matched[2]
path := matched[3]
qidx := strings.Index(path, "?")
if qidx == -1 {
qidx = len(path)
}
var u url.URL
u.Scheme = "ssh"
u.User = url.User(user)
u.Host = host
u.Path = path[0:qidx]
if qidx < len(path) {
q, err := url.ParseQuery(path[qidx+1:])
if err != nil {
return nil, fmt.Errorf("error parsing Git SSH URL: %s", err)
}
u.RawQuery = q.Encode()
}
return &u, nil
}