// Package router maps the hostname a client typed to a configured server. package router import ( "sort" "strings" "git.veillax.com/Veillax/spinner/internal/config" ) type Router struct { exact map[string]*config.Server wild []wildcard } type wildcard struct { suffix string // ".example.com" srv *config.Server } // Normalize lowercases a handshake host and strips the trailing dot and any // Forge "\0FML\0" suffix. func Normalize(host string) string { if i := strings.IndexByte(host, 0); i >= 0 { host = host[:i] } return strings.ToLower(strings.TrimRight(host, ".")) } // New builds a router. Exact domains win; wildcards (*.example.com) match any // depth of subdomain but not the apex, and longer suffixes win over shorter. func New(cfg *config.Config) *Router { r := &Router{exact: map[string]*config.Server{}} for i := range cfg.Servers { s := &cfg.Servers[i] for _, d := range s.Domains { d = strings.ToLower(strings.TrimRight(d, ".")) if rest, ok := strings.CutPrefix(d, "*."); ok { r.wild = append(r.wild, wildcard{"." + rest, s}) } else if _, dup := r.exact[d]; !dup { r.exact[d] = s } } } sort.SliceStable(r.wild, func(i, j int) bool { return len(r.wild[i].suffix) > len(r.wild[j].suffix) }) return r } func (r *Router) Match(host string) *config.Server { host = Normalize(host) if s, ok := r.exact[host]; ok { return s } for _, w := range r.wild { if strings.HasSuffix(host, w.suffix) && len(host) > len(w.suffix) { return w.srv } } return nil }