mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
179 lines
3.9 KiB
Go
179 lines
3.9 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
|
|
package rnsgit
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
// ParseRNSURL parses rns://hash/group/repo or rns://hash/group.
|
|
func ParseRNSURL(raw string) (destHex, group, repo string, err error) {
|
|
raw = strings.TrimSpace(raw)
|
|
if !strings.HasPrefix(strings.ToLower(raw), ProtoRNS) {
|
|
return "", "", "", fmt.Errorf("invalid protocol, want rns://")
|
|
}
|
|
rest := raw[len(ProtoRNS):]
|
|
parts := strings.SplitN(rest, "/", 3)
|
|
if len(parts) < 2 {
|
|
return "", "", "", fmt.Errorf("invalid URL format, use rns://<hash>/<group>/<repo>")
|
|
}
|
|
destHex = parts[0]
|
|
group = parts[1]
|
|
if len(parts) == 3 {
|
|
repo = parts[2]
|
|
}
|
|
if len(destHex) != 32 {
|
|
return "", "", "", fmt.Errorf("invalid destination hash length")
|
|
}
|
|
if _, err := hex.DecodeString(destHex); err != nil {
|
|
return "", "", "", fmt.Errorf("invalid destination hash: %w", err)
|
|
}
|
|
return destHex, group, repo, nil
|
|
}
|
|
|
|
// RepoPath returns group/repo.
|
|
func RepoPath(group, repo string) string {
|
|
return group + "/" + repo
|
|
}
|
|
|
|
// SanRef validates a git ref name.
|
|
func SanRef(ref string) string {
|
|
if ref == "" {
|
|
return ""
|
|
}
|
|
if strings.HasPrefix(ref, "-") || strings.HasPrefix(ref, "/") {
|
|
return ""
|
|
}
|
|
if strings.HasSuffix(ref, "/") || strings.HasSuffix(ref, ".") {
|
|
return ""
|
|
}
|
|
if strings.Contains(ref, " ") || !strings.Contains(ref, "/") {
|
|
return ""
|
|
}
|
|
if strings.Contains(ref, "..") || strings.Contains(ref, "/.") || strings.Contains(ref, "//") || strings.Contains(ref, `\`) {
|
|
return ""
|
|
}
|
|
for comp := range strings.SplitSeq(ref, "/") {
|
|
if strings.HasSuffix(comp, ".lock") {
|
|
return ""
|
|
}
|
|
}
|
|
for _, c := range ref {
|
|
if c < 40 {
|
|
return ""
|
|
}
|
|
}
|
|
if strings.ContainsAny(ref, "~^:?*[@") || ref == "@" {
|
|
return ""
|
|
}
|
|
if strings.Contains(ref, "@{") {
|
|
return ""
|
|
}
|
|
return ref
|
|
}
|
|
|
|
// SanRefs validates a list of refs.
|
|
func SanRefs(refs []string) []string {
|
|
if len(refs) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]string, 0, len(refs))
|
|
for _, r := range refs {
|
|
if SanRef(r) == "" {
|
|
return nil
|
|
}
|
|
out = append(out, r)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// SanSHA validates a 40-char git object id.
|
|
func SanSHA(sha string) string {
|
|
if len(sha) < 40 {
|
|
return ""
|
|
}
|
|
if _, err := hex.DecodeString(sha); err != nil {
|
|
return ""
|
|
}
|
|
return sha
|
|
}
|
|
|
|
// ParseRepoPath splits group/repo request paths.
|
|
func ParseRepoPath(path string) (group, repo string, ok bool) {
|
|
parts := strings.Split(path, "/")
|
|
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
|
return "", "", false
|
|
}
|
|
if len(parts[0]) > 256 || len(parts[1]) > 256 {
|
|
return "", "", false
|
|
}
|
|
return parts[0], parts[1], true
|
|
}
|
|
|
|
// EscapeGitStdout escapes a value for git remote-helper output.
|
|
func EscapeGitStdout(value string) string {
|
|
var b strings.Builder
|
|
b.WriteByte('"')
|
|
for _, c := range value {
|
|
switch c {
|
|
case '\\':
|
|
b.WriteString(`\\`)
|
|
case '"':
|
|
b.WriteString(`\"`)
|
|
case '\n':
|
|
b.WriteString(`\n`)
|
|
case '\t':
|
|
b.WriteString(`\t`)
|
|
case '\r':
|
|
b.WriteString(`\r`)
|
|
default:
|
|
if c < 32 || c > 126 {
|
|
b.WriteString(fmt.Sprintf(`\x%02x`, c))
|
|
} else {
|
|
b.WriteRune(c)
|
|
}
|
|
}
|
|
}
|
|
b.WriteByte('"')
|
|
return b.String()
|
|
}
|
|
|
|
func isGitBundle(data []byte) bool {
|
|
return strings.HasPrefix(string(data), "# v2 git bundle")
|
|
}
|
|
|
|
func localGitCmd(args ...string) *exec.Cmd {
|
|
cmd := exec.Command("git", args...) // #nosec G204 -- fixed git binary
|
|
env := envWithoutGitDir()
|
|
if dir := os.Getenv("GIT_DIR"); dir != "" {
|
|
env = append(env, "GIT_DIR="+dir)
|
|
}
|
|
if wt := os.Getenv("GIT_WORK_TREE"); wt != "" {
|
|
env = append(env, "GIT_WORK_TREE="+wt)
|
|
cmd.Dir = wt
|
|
}
|
|
cmd.Env = env
|
|
return cmd
|
|
}
|
|
|
|
// GitCleanEnv returns a copy of the environment without GIT_DIR or GIT_WORK_TREE.
|
|
func GitCleanEnv() []string {
|
|
return envWithoutGitDir()
|
|
}
|
|
|
|
func envWithoutGitDir() []string {
|
|
env := os.Environ()
|
|
out := make([]string, 0, len(env))
|
|
for _, e := range env {
|
|
if strings.HasPrefix(e, "GIT_DIR=") || strings.HasPrefix(e, "GIT_WORK_TREE=") {
|
|
continue
|
|
}
|
|
out = append(out, e)
|
|
}
|
|
return out
|
|
}
|