package gitx import ( "regexp" "strings" ) // namePattern is the one rule for account and repository names alike. Chapter 42.5. var namePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,38}$`) // reserved holds every top-level route name, which TestEveryTopLevelRouteIsAReservedName derives. var reserved = map[string]bool{ // routed today, per appendix C "new": true, "signup": true, "signin": true, "signout": true, "auth": true, "keys": true, "gpgkeys": true, "tokens": true, "search": true, "inbox": true, "runner": true, "raw": true, // held for later "static": true, "api": true, "admin": true, "about": true, "card": true, } // ValidName reports whether s is a usable account name, which a top-level route can shadow. func ValidName(s string) bool { return namePattern.MatchString(s) && !reserved[s] } // ValidRepoName drops the reserved list, because a repository is a second path segment and every reserved name is a first one. func ValidRepoName(s string) bool { return namePattern.MatchString(s) } // Reserved reports a name refused only because a route owns it, which signup says out loud. func Reserved(s string) bool { return reserved[s] } // ValidRef mirrors check-ref-format and runs first, so nothing malformed reaches an argument. func ValidRef(r string) bool { if r == "" || len(r) > 255 { return false } if strings.HasSuffix(r, "/") || strings.HasSuffix(r, ".lock") { return false } if strings.Contains(r, "..") || strings.Contains(r, "@{") || strings.Contains(r, "//") { return false } if strings.ContainsAny(r, " ~^:?*[\\\x7f") { return false } for _, c := range r { if c < 0x20 { return false } } for _, part := range strings.Split(r, "/") { if part == "" || strings.HasPrefix(part, ".") || strings.HasSuffix(part, ".") { return false } // Any component, because git is handed the component alone and a dash makes it a flag. if strings.HasPrefix(part, "-") { return false } } return true } // ValidRev guards a revision from a URL, and above all stops a leading dash becoming a flag. func ValidRev(r string) bool { if r == "" || len(r) > 255 { return false } if strings.HasPrefix(r, "-") || strings.Contains(r, "..") && !strings.Contains(r, "...") { // Callers build their own ranges, and the compare view splits `...` before it gets here. return false } if strings.Contains(r, "...") { return false } for _, c := range r { switch { case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c >= '0' && c <= '9': case c == '/' || c == '.' || c == '-' || c == '_' || c == '~' || c == '^' || c == '@': default: return false } } for _, part := range strings.Split(r, "/") { if part == "" || strings.HasPrefix(part, ".") || strings.HasPrefix(part, "-") { return false } } return true } // ValidPath guards a path in a git tree against the same two mistakes: escaping up, and flags. func ValidPath(p string) bool { if p == "" || len(p) > 4096 || strings.HasPrefix(p, "-") || strings.HasPrefix(p, "/") { return false } for _, part := range strings.Split(p, "/") { if part == "" || part == "." || part == ".." { return false } } for _, c := range p { if c < 0x20 || c == 0x7f { return false } } return true }