mirror of
https://github.com/larksuite/cli.git
synced 2026-07-06 16:18:05 +08:00
115 lines
3.1 KiB
Go
115 lines
3.1 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package gitcred
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/larksuite/cli/errs"
|
|
)
|
|
|
|
func NormalizeGitHTTPURL(raw string) (string, error) {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "git_http_url is empty")
|
|
}
|
|
u, err := url.Parse(raw)
|
|
if err != nil {
|
|
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "invalid git_http_url %q: %s", raw, err).WithCause(err)
|
|
}
|
|
return normalizeParsedURL(u)
|
|
}
|
|
|
|
func NormalizeCredentialInput(input CredentialInput) (string, error) {
|
|
protocol := strings.TrimSpace(input.Protocol)
|
|
host := strings.TrimSpace(input.Host)
|
|
if protocol == "" || host == "" {
|
|
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "git credential input must include protocol and host")
|
|
}
|
|
u := &url.URL{
|
|
Scheme: protocol,
|
|
Host: host,
|
|
Path: input.Path,
|
|
}
|
|
return normalizeParsedURL(u)
|
|
}
|
|
|
|
func normalizeParsedURL(u *url.URL) (string, error) {
|
|
scheme := strings.ToLower(strings.TrimSpace(u.Scheme))
|
|
if scheme != "http" && scheme != "https" {
|
|
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "git credential only supports http/https URLs")
|
|
}
|
|
host := normalizeHost(scheme, u.Host)
|
|
if host == "" {
|
|
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "git_http_url host is empty")
|
|
}
|
|
cleanPath := cleanURLPath(u.EscapedPath())
|
|
normalized := (&url.URL{Scheme: scheme, Host: host, Path: cleanPath}).String()
|
|
if normalized != scheme+"://"+host+"/" {
|
|
normalized = strings.TrimRight(normalized, "/")
|
|
}
|
|
return normalized, nil
|
|
}
|
|
|
|
func normalizeHost(scheme, host string) string {
|
|
host = strings.ToLower(strings.TrimSpace(host))
|
|
if host == "" {
|
|
return ""
|
|
}
|
|
name, port, err := net.SplitHostPort(host)
|
|
if err == nil {
|
|
if (scheme == "https" && port == "443") || (scheme == "http" && port == "80") {
|
|
return normalizeHostname(name)
|
|
}
|
|
return net.JoinHostPort(strings.ToLower(name), port)
|
|
}
|
|
return normalizeHostname(host)
|
|
}
|
|
|
|
func normalizeHostname(host string) string {
|
|
host = strings.ToLower(strings.TrimSpace(host))
|
|
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
|
name := strings.TrimPrefix(strings.TrimSuffix(host, "]"), "[")
|
|
if ip := net.ParseIP(name); ip != nil && ip.To4() == nil {
|
|
return joinHostWithoutPort(name)
|
|
}
|
|
return host
|
|
}
|
|
if ip := net.ParseIP(host); ip != nil && ip.To4() == nil {
|
|
return joinHostWithoutPort(host)
|
|
}
|
|
return host
|
|
}
|
|
|
|
func joinHostWithoutPort(host string) string {
|
|
joined := net.JoinHostPort(host, "")
|
|
return strings.TrimSuffix(joined, ":")
|
|
}
|
|
|
|
func cleanURLPath(rawPath string) string {
|
|
if rawPath == "" {
|
|
return "/"
|
|
}
|
|
decoded, err := url.PathUnescape(rawPath)
|
|
if err != nil {
|
|
decoded = rawPath
|
|
}
|
|
if !strings.HasPrefix(decoded, "/") {
|
|
decoded = "/" + decoded
|
|
}
|
|
return path.Clean(decoded)
|
|
}
|
|
|
|
func BuildPATRef(profile ProfileContext, appID string) string {
|
|
seed := fmt.Sprintf("%s\x00%s", profile.UserOpenID, appID)
|
|
sum := sha256.Sum256([]byte(seed))
|
|
return "app-git-pat:" + hex.EncodeToString(sum[:16])
|
|
}
|