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