sr-71/routes.go

149 lines
3.3 KiB
Go

package main
import (
"context"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"os"
"path/filepath"
"sort"
"strings"
sr "tildegit.org/tjp/sliderule"
"tildegit.org/tjp/sliderule/contrib/cgi"
"tildegit.org/tjp/sliderule/contrib/fs"
"tildegit.org/tjp/sliderule/contrib/tlsauth"
"tildegit.org/tjp/sliderule/finger"
"tildegit.org/tjp/sliderule/gemini"
"tildegit.org/tjp/sliderule/gemini/gemtext/atomconv"
"tildegit.org/tjp/sliderule/gopher/gophermap"
"tildegit.org/tjp/sliderule/logging"
)
func geminiRouter(conf config) sr.Handler {
fsys := os.DirFS(conf.geminiRoot)
privileged := tlsAuth(conf.privilegedUsers)
router := &sr.Router{}
router.Route(
"/*",
gemini.GeminiOnly(true)(sr.FallthroughHandler(
fs.TitanUpload(privileged, conf.geminiRoot)(postUploadRedirect),
fs.GeminiFileHandler(fsys),
fs.GeminiDirectoryDefault(fsys, "index.gmi"),
fs.GeminiDirectoryListing(fsys, nil),
)),
)
router.Route(
"/cgi-bin/*",
gemini.GeminiOnly(false)(cgi.GeminiCGIDirectory(
"/cgi-bin/",
strings.Join([]string{".", strings.Trim(conf.geminiRoot, "/"), "cgi-bin"}, "/"),
)),
)
router.Route(
"/cgi-bin/private/*",
gemini.GeminiOnly(false)(tlsauth.GeminiAuth(privileged)(
cgi.GeminiCGIDirectory("/cgi-bin/private/", strings.Join([]string{
".",
strings.Trim(conf.geminiRoot, "/"),
"cgi-bin",
"private",
}, "/")),
)),
)
h := router.Handler()
if conf.geminiAutoAtom {
h = atomconv.Auto(h)
}
return h
}
func gopherRouter(conf config) sr.Handler {
settings := gophermap.FileSystemSettings{
ParseExtended: true,
Exec: true,
ListUsers: false,
DirMaps: []string{"gophermap"},
DirTag: "gophertag",
}
router := &sr.Router{}
router.Route(
"/*",
sr.FallthroughHandler(
cgi.ExecGopherMaps("/", conf.gopherRoot, &settings),
fs.GopherFileHandler(conf.gopherRoot, &settings),
fs.GopherDirectoryDefault(conf.gopherRoot, &settings),
fs.GopherDirectoryListing(conf.gopherRoot, &settings),
),
)
router.Route(
"/cgi-bin/*",
cgi.GopherCGIDirectory("/cgi-bin/", filepath.Join(conf.gopherRoot, "cgi-bin"), &settings),
)
return router.Handler()
}
var postUploadRedirect = sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response {
u := *request.URL
u.Path = strings.SplitN(u.Path, ";", 2)[0]
u.Scheme = "gemini"
return gemini.Redirect(u.String())
})
func tlsAuth(uploaders []string) tlsauth.Approver {
sort.Strings(uploaders)
return func(cert *x509.Certificate) bool {
raw := sha256.Sum256(cert.Raw)
user := hex.EncodeToString(raw[:])
_, found := sort.Find(len(uploaders), func(i int) int {
switch {
case uploaders[i] < user:
return 1
case uploaders[i] == user:
return 0
default:
return -1
}
})
return found
}
}
func fingerHandler(conf config) sr.Handler {
return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response {
name := strings.TrimPrefix(request.Path, "/")
if name == "" {
return finger.Error("listings not permitted")
}
path, ok := conf.fingerResponses[strings.ToLower(name)]
if !ok {
return finger.Error("user not found")
}
file, err := os.Open(path)
if err != nil {
ctx.Value("errorlog").(logging.Logger).Log(
"msg", "finger response file open error",
"error", err,
)
}
return finger.Success(file)
})
}