package main import ( "context" "crypto/tls" "fmt" "strings" sr "tildegit.org/tjp/sliderule" "tildegit.org/tjp/sliderule/contrib/cgi" "tildegit.org/tjp/sliderule/contrib/fs" "tildegit.org/tjp/sliderule/gemini" "tildegit.org/tjp/sliderule/gemini/gemtext/atomconv" "tildegit.org/tjp/sliderule/logging" "tildegit.org/tjp/syw" ) func buildGeminiServers(servers []Server, config *Configuration) ([]sr.Server, error) { _, info, _, errlog := Loggers(config) groups := map[string][]*Server{} for i := range servers { addr := fmt.Sprintf("%s:%d", servers[i].IP.String(), servers[i].Port) grp, ok := groups[addr] if !ok { groups[addr] = []*Server{&servers[i]} } else { groups[addr] = append(grp, &servers[i]) } } result := []sr.Server{} for addr, configs := range groups { _ = info.Log("msg", "starting gemini server", "addr", addr) var handler sr.Handler if len(configs) == 1 { handler = routes(*configs[0]) } else { mapping := map[string]sr.Handler{} for _, config := range configs { router := routes(*config) for _, hostname := range config.Hostnames { mapping[hostname] = router } } handler = sr.VirtualHosts(mapping, sr.HandlerFunc(func(_ context.Context, _ *sr.Request) *sr.Response { return gemini.RefuseProxy("Proxy request refused") })) } var hostname string for _, conf := range configs { if len(conf.Hostnames) > 0 { hostname = conf.Hostnames[0] break } } tlsConfigs := map[string]*tls.Config{} var fallback *tls.Config = nil for _, config := range configs { if len(config.Hostnames) > 0 && config.TLS != nil { for _, hostname := range config.Hostnames { tlsConfigs[hostname] = config.TLS } } else { fallback = config.TLS } } tlsConfig := gemini.MultiTLS(tlsConfigs, fallback) gemsrv, err := gemini.NewServer( context.Background(), hostname, "tcp", addr, logging.LogRequests(info)(handler), errlog, tlsConfig, ) if err != nil { return nil, err } result = append(result, gemsrv) } return result, nil } func addGeminiRoute(router *sr.Router, route RouteDirective) { switch route.Type { case "static": addGeminiStaticRoute(router, route) case "cgi": buildAndAddRoute(router, route, func(route RouteDirective) sr.Handler { handler := cgi.GeminiCGIDirectory(route.FsPath, route.URLPath, route.Modifiers.ExecCmd) if route.Modifiers.AutoAtom { handler = atomconv.Auto(handler) } return GeminiAuthMiddleware(route.Modifiers.Auth)(handler) }) case "git": addGeminiGitRoute(router, route) } } func addGeminiStaticRoute(router *sr.Router, route RouteDirective) { buildAndAddRoute(router, route, func(route RouteDirective) sr.Handler { handlers := []sr.Handler{} if route.Modifiers.Exec { handlers = append(handlers, cgi.GeminiCGIDirectory(route.FsPath, route.URLPath, route.Modifiers.ExecCmd)) } handlers = append(handlers, fs.GeminiFileHandler(route.FsPath, route.URLPath)) if route.Modifiers.DirDefault != "" { handlers = append( handlers, fs.GeminiDirectoryDefault(route.FsPath, route.URLPath, route.Modifiers.DirDefault), ) } if route.Modifiers.DirList { handlers = append(handlers, fs.GeminiDirectoryListing(route.FsPath, route.URLPath, nil)) } handler := sr.FallthroughHandler(handlers...) if route.Modifiers.AutoAtom { handler = atomconv.Auto(handler) } handler = GeminiAuthMiddleware(route.Modifiers.Auth)(handler) if route.Modifiers.Titan != nil { titan := fs.TitanUpload(route.FsPath, route.URLPath, route.Modifiers.Titan.Strategy.Approve)(handler) handler = sr.Filter(func(ctx context.Context, request *sr.Request) bool { return request.Scheme == "titan" }, handler)(titan) } return handler }) } func addGeminiGitRoute(router *sr.Router, route RouteDirective) { buildAndAddRoute(router, route, func(route RouteDirective) sr.Handler { subrouter := syw.GeminiRouter(route.FsPath, route.Modifiers.Templates) handler := sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { reqclone := cloneRequest(request) reqclone.Path = strings.TrimPrefix(reqclone.Path, route.URLPath) handler, params := subrouter.Match(reqclone) if handler == nil { return nil } return handler.Handle(context.WithValue(ctx, sr.RouteParamsKey, params), request) }) if route.Modifiers.AutoAtom { handler = atomconv.Auto(handler) } return GeminiAuthMiddleware(route.Modifiers.Auth)(handler) }) }