summaryrefslogtreecommitdiff
path: root/routes.go
blob: 35bdfc1b23e9bcaa7300525f478e538436298eaa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package main

import (
	"context"
	"crypto/sha256"
	"crypto/x509"
	"encoding/hex"
	"os"
	"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/logging"
)

func geminiRouter(conf config) sr.Handler {
	fsys := os.DirFS(conf.geminiRoot)

	router := &sr.Router{}

	router.Route(
		"/*",
		gemini.GeminiOnly(true)(sr.FallthroughHandler(
			fs.TitanUpload(tlsAuth(conf.uploaderFingerprints), 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/", "./cgi-bin/")),
	)

	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)
	})
}