package handlers import ( "errors" "net/url" "regexp" "strings" ) // Repo URL validation errors. var ( errRepoURLEmpty = errors.New("repository URL must not be empty") errRepoURLScheme = errors.New("file:// URLs are not allowed for security reasons") errRepoURLInvalid = errors.New("repository URL must use https://, http://, ssh://, git://, or git@host:path format") errRepoURLNoHost = errors.New("repository URL must include a host") errRepoURLNoPath = errors.New("repository URL must include a path") ) // scpLikeRepoRe matches SCP-like git URLs: git@host:path (e.g. git@github.com:user/repo.git). var scpLikeRepoRe = regexp.MustCompile(`^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+:.+$`) // validateRepoURL checks that the given repository URL is valid and uses an allowed scheme. func validateRepoURL(repoURL string) error { if strings.TrimSpace(repoURL) == "" { return errRepoURLEmpty } // Check for SCP-like git URLs first (git@host:path) if scpLikeRepoRe.MatchString(repoURL) { return nil } // Reject file:// explicitly if strings.HasPrefix(strings.ToLower(repoURL), "file://") { return errRepoURLScheme } // Parse as standard URL parsed, err := url.Parse(repoURL) if err != nil { return errRepoURLInvalid } // Must have a recognized scheme switch strings.ToLower(parsed.Scheme) { case "https", "http", "ssh", "git": // OK default: return errRepoURLInvalid } if parsed.Host == "" { return errRepoURLNoHost } if parsed.Path == "" || parsed.Path == "/" { return errRepoURLNoPath } return nil }