package workspace import ( "errors" "path/filepath" "regexp" "strings" "github.com/google/uuid" ) // 输入校验正则。改动需要同步更新 spec §3.6 与 dto.go 的错误消息。 var ( slugRe = regexp.MustCompile(`^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$`) branchRe = regexp.MustCompile(`^[A-Za-z0-9._/-]{1,200}$`) ) // ValidateSlug 报告 slug 是否合法。 func ValidateSlug(slug string) error { if !slugRe.MatchString(slug) { return errors.New("slug must be lowercase alnum + '-' (1..32 chars), no leading/trailing dash") } return nil } // ValidateBranch 报告 branch 名是否合法(防止被 git 当成 flag)。 func ValidateBranch(b string) error { if !branchRe.MatchString(b) { return errors.New("branch must match [A-Za-z0-9._/-]{1,200}") } if strings.HasPrefix(b, "-") || strings.HasPrefix(b, "+") || strings.HasPrefix(b, "/") { return errors.New("branch must not start with '-', '+', or '/'") } if strings.Contains(b, "..") { return errors.New("branch must not contain '..'") } return nil } // ValidateRemoteURL 检查 scheme 白名单,并拒绝 scheme:// 形式中内嵌的 userinfo(凭据)。 func ValidateRemoteURL(u string) error { switch { case strings.HasPrefix(u, "https://"), strings.HasPrefix(u, "http://"), strings.HasPrefix(u, "ssh://"): // 拒绝 scheme://user:pass@host 形式的内嵌凭据:内嵌凭据会绕过本系统凭据 // 存储并短路数据库 PAT。注意 ssh://git@host 这类裸用户名(无 ":" 密码)是 // 合法 SSH 形式,仅当 userinfo 中带 ":" 密码时才判定为内嵌凭据。 rest := u[strings.Index(u, "://")+len("://"):] authority := rest if i := strings.IndexByte(rest, '/'); i >= 0 { authority = rest[:i] } if at := strings.IndexByte(authority, '@'); at >= 0 { if strings.ContainsRune(authority[:at], ':') { return errors.New("git_remote_url must not contain embedded credentials") } } return nil case strings.HasPrefix(u, "git@"): return nil } return errors.New("git_remote_url must be https/http/ssh/git@ scheme") } // MainPath 计算 workspace.main_path 绝对路径。dataDir 是配置中的 ${DATA_DIR}。 func MainPath(dataDir string, wsID uuid.UUID) string { return filepath.Join(dataDir, "workspaces", wsID.String(), "main") } // WorktreePath 计算支线 worktree 绝对路径。branch slug 化后嵌入路径。 func WorktreePath(dataDir string, wsID uuid.UUID, branch string) string { return filepath.Join(dataDir, "workspaces", wsID.String(), ".worktrees", branchSlug(branch)) } // TmpDir 是 git CLI 凭据/known_hosts 临时文件根目录。启动期需清空。 func TmpDir(dataDir string) string { return filepath.Join(dataDir, "tmp") } // branchSlug 把 branch 名转成可作为目录名的字符串。 // // 规则:把 '/' 替换为 '_';其他非法字符保留(branchRe 已限制)。 func branchSlug(b string) string { return strings.ReplaceAll(b, "/", "_") }