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 }