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 log.Println("Index request")
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 userName := filepath.Clean(strings.Split(r.URL.Host, ".")[0]) // clean probably unnecessary
83 fileName := filepath.Clean(r.URL.Path)
84 if fileName == "/" {
85 fileName = "index.gmi"
86 } else if strings.HasPrefix(fileName, "/"+HiddenFolder) {
87 w.WriteStatus(gmi.StatusNotFound)
88 return
89 }
90 log.Println("Request for gemini file", fileName, "for user", userName)
91 fullPath := path.Join(c.FilesDirectory, userName, fileName)
92 if fileName == "/gemlog" { // temp hack
93 _, err := os.Stat(path.Join(fullPath, "index.gmi"))
94 if err != nil {
95 w.SetMediaType("text/gemini")
96 io.Copy(w, strings.NewReader(generateGemfeedPage(userName)))
97 return
98 }
99 } else if fileName == "/gemlog/atom.xml" {
100 _, err := os.Stat(fullPath)
101 if err != nil {
102 w.SetMediaType("application/atom+xml")
103 feed := generateFeedFromUser(userName)
104 atomString := feed.toAtomFeed()
105 io.Copy(w, strings.NewReader(atomString))
106 return
107 }
108 }
109
110 gmi.ServeFile(w, gmi.Dir(path.Join(c.FilesDirectory, userName)), fileName)
111}
112
113func runGeminiServer() {
114 log.Println("Starting gemini server")
115 var err error
116 gt, err = template.ParseGlob(path.Join(c.TemplatesDirectory, "*.gmi"))
117 if err != nil {
118 log.Fatal(err)
119 }
120 var server gmi.Server
121 server.ReadTimeout = 1 * time.Minute
122 server.WriteTimeout = 2 * time.Minute
123
124 hostname := strings.SplitN(c.Host, ":", 2)[0]
125 // is this necc?
126 err = server.Certificates.Load(c.GeminiCertStore)
127 if err != nil {
128 }
129 server.CreateCertificate = func(h string) (tls.Certificate, error) {
130 log.Println("Generating certificate for", h)
131 return gmi.CreateCertificate(gmi.CertificateOptions{
132 Subject: pkix.Name{
133 CommonName: hostname,
134 },
135 DNSNames: []string{h},
136 Duration: time.Hour * 760, // one month
137 })
138 }
139
140 var mux gmi.ServeMux
141 // replace with wildcard cert
142 mux.HandleFunc("/", gmiIndex)
143
144 var wildcardMux gmi.ServeMux
145 wildcardMux.HandleFunc("/", gmiPage)
146 server.Register(hostname, &mux)
147 server.Register("*."+hostname, &wildcardMux)
148
149 err = server.ListenAndServe()
150 if err != nil {
151 log.Fatal(err)
152 }
153}