gemini.go (view raw)
1package main
2
3import (
4 "bytes"
5 "crypto/tls"
6 "crypto/x509/pkix"
7 gmi "git.sr.ht/~adnano/go-gemini"
8 "io"
9 "io/ioutil"
10 "log"
11 "os"
12 "path"
13 "path/filepath"
14 "strings"
15 "text/template"
16 "time"
17)
18
19var gt *template.Template
20
21func generateGemfeedPage(user string) string {
22 feed := generateFeedFromUser(user)
23 data := struct {
24 Host string
25 Title string
26 FeedEntries []FeedEntry
27 }{c.Host, feed.Title, feed.Entries}
28 var buff bytes.Buffer
29 gt.ExecuteTemplate(&buff, "gemfeed.gmi", data)
30 return buff.String()
31}
32
33func generateFolderPage(fullpath string) string {
34 files, _ := ioutil.ReadDir(fullpath)
35 var renderedFiles = []File{}
36 for _, file := range files {
37 // Very awkward
38 res := fileFromPath(path.Join(fullpath, file.Name()))
39 renderedFiles = append(renderedFiles, res)
40 }
41 var buff bytes.Buffer
42 data := struct {
43 Host string
44 Folder string
45 Files []File
46 }{c.Host, getLocalPath(fullpath), renderedFiles}
47 err := gt.ExecuteTemplate(&buff, "folder.gmi", data)
48 if err != nil {
49 log.Println(err)
50 return ""
51 }
52 return buff.String()
53}
54
55func gmiIndex(w *gmi.ResponseWriter, r *gmi.Request) {
56 logGemini(r) // TODO move into wrapper
57 t, err := template.ParseFiles("templates/index.gmi")
58 if err != nil {
59 log.Fatal(err)
60 }
61 files, err := getIndexFiles(false)
62 users, err := getActiveUserNames()
63 if err != nil {
64 log.Println(err)
65 w.WriteHeader(40, "Internal server error")
66 }
67 data := struct {
68 Host string
69 SiteTitle string
70 Files []*File
71 Users []string
72 }{
73 Host: c.Host,
74 SiteTitle: c.SiteTitle,
75 Files: files,
76 Users: users,
77 }
78 t.Execute(w, data)
79}
80
81func gmiPage(w *gmi.ResponseWriter, r *gmi.Request) {
82 logGemini(r) // TODO move into wrapper
83 var userName string
84 custom := domains[r.Host]
85 if custom != "" {
86 userName = custom
87 } else {
88 userName = filepath.Clean(strings.Split(r.URL.Host, ".")[0]) // clean probably unnecessary
89 }
90 fileName := filepath.Clean(r.URL.Path)
91 if fileName == "/" {
92 fileName = "index.gmi"
93 } else if strings.HasPrefix(fileName, "/"+HiddenFolder) {
94 w.WriteStatus(gmi.StatusNotFound)
95 return
96 }
97 fullPath := path.Join(c.FilesDirectory, userName, fileName)
98 if fileName == "/gemlog" { // temp hack
99 _, err := os.Stat(path.Join(fullPath, "index.gmi"))
100 if err != nil {
101 w.SetMediaType("text/gemini")
102 io.Copy(w, strings.NewReader(generateGemfeedPage(userName)))
103 return
104 }
105 } else if fileName == "/gemlog/atom.xml" {
106 _, err := os.Stat(fullPath)
107 if err != nil {
108 w.SetMediaType("application/atom+xml")
109 feed := generateFeedFromUser(userName)
110 atomString := feed.toAtomFeed()
111 io.Copy(w, strings.NewReader(atomString))
112 return
113 }
114 }
115
116 gmi.ServeFile(w, gmi.Dir(path.Join(c.FilesDirectory, userName)), fileName)
117}
118
119func runGeminiServer() {
120 log.Println("Starting gemini server")
121 var err error
122 gt, err = template.ParseGlob(path.Join(c.TemplatesDirectory, "*.gmi"))
123 if err != nil {
124 log.Fatal(err)
125 }
126 var server gmi.Server
127 server.ReadTimeout = 1 * time.Minute
128 server.WriteTimeout = 2 * time.Minute
129
130 hostname := strings.SplitN(c.Host, ":", 2)[0]
131 // is this necc?
132 err = server.Certificates.Load(c.GeminiCertStore)
133 if err != nil {
134 }
135 server.CreateCertificate = func(h string) (tls.Certificate, error) {
136 log.Println("Generating certificate for", h)
137 return gmi.CreateCertificate(gmi.CertificateOptions{
138 Subject: pkix.Name{
139 CommonName: hostname,
140 },
141 DNSNames: []string{h},
142 Duration: time.Hour * 8760 * 100, // 100 years
143 })
144 }
145
146 var mux gmi.ServeMux
147 // replace with wildcard cert
148 mux.HandleFunc("/", gmiIndex)
149
150 var wildcardMux gmi.ServeMux
151 wildcardMux.HandleFunc("/", gmiPage)
152 server.Register(hostname, &mux)
153 server.Register("*."+hostname, &wildcardMux)
154
155 err = server.ListenAndServe()
156 if err != nil {
157 log.Fatal(err)
158 }
159}