package core
import (
"fmt"
"regexp"
"strings"
)
// MaxNameLen is the maximum length of a database name. Names double as SQL
// database identifiers, so they are kept short and conservative.
const MaxNameLen = 64
// nameRe matches a valid database (or owner) name: an alphanumeric first and
// last character, with alphanumerics, hyphens and underscores in between. It
// structurally forbids "." and ".." (no dots at all) and leading/trailing
// separators.
var nameRe = regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$`)
// ValidateName reports whether name is an acceptable database name. It rejects
// the empty string, names longer than MaxNameLen, the traversal names "." and
// ".." explicitly, and anything not matching nameRe.
func ValidateName(name string) error {
if name == "" {
return fmt.Errorf("name must not be empty")
}
if name == "." || name == ".." {
return fmt.Errorf("name %q is not allowed", name)
}
if len(name) > MaxNameLen {
return fmt.Errorf("name is too long (%d > %d)", len(name), MaxNameLen)
}
if !nameRe.MatchString(name) {
return fmt.Errorf("name %q must match %s", name, nameRe.String())
}
return nil
}
// ParseRepoPath splits a repository path into its owner and database segments.
// It accepts "~user/db", "user/db", and leading/trailing slashes. It rejects
// empty segments, ".."/"." traversal segments, and any path that does not have
// exactly two segments. The returned owner never carries a leading "~".
//
// ParseRepoPath validates structure only; owner and db are not name-validated
// here (callers that touch disk or SQL must additionally run ValidateName).
func ParseRepoPath(path string) (owner, db string, err error) {
trimmed := strings.Trim(path, "/")
if trimmed == "" {
return "", "", fmt.Errorf("empty repo path")
}
segs := strings.Split(trimmed, "/")
if len(segs) != 2 {
return "", "", fmt.Errorf("repo path %q must have exactly 2 segments, got %d", path, len(segs))
}
for _, s := range segs {
if s == "" {
return "", "", fmt.Errorf("repo path %q has an empty segment", path)
}
if s == "." || s == ".." {
return "", "", fmt.Errorf("repo path %q contains a traversal segment %q", path, s)
}
}
owner = strings.TrimPrefix(segs[0], "~")
db = segs[1]
if owner == "" {
return "", "", fmt.Errorf("repo path %q has an empty owner", path)
}
return owner, db, nil
}