all repos — flounder @ dc25dfe97957a301e6122d32ad4e6cbf45de2ecc

A small site builder for the Gemini protocol

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	"path"
 12	"path/filepath"
 13	"strings"
 14	"text/template"
 15	"time"
 16)
 17
 18var gt *template.Template
 19
 20func generateGemfeedPage(user string) string {
 21	feedItems := generateFeedFromUser(user)
 22	data := struct {
 23		Host        string
 24		Title       string
 25		FeedEntries []FeedEntry
 26	}{c.Host, user + "'s Gemlog", feedItems}
 27	var buff bytes.Buffer
 28	gt.ExecuteTemplate(&buff, "gemfeed.gmi", data)
 29	return buff.String()
 30}
 31
 32func generateFolderPage(fullpath string) string {
 33	files, _ := ioutil.ReadDir(fullpath)
 34	var renderedFiles = []File{}
 35	for _, file := range files {
 36		// Very awkward
 37		res := fileFromPath(path.Join(fullpath, file.Name()))
 38		renderedFiles = append(renderedFiles, res)
 39	}
 40	var buff bytes.Buffer
 41	data := struct {
 42		Host   string
 43		Folder string
 44		Files  []File
 45	}{c.Host, getLocalPath(fullpath), renderedFiles}
 46	err := gt.ExecuteTemplate(&buff, "folder.gmi", data)
 47	if err != nil {
 48		log.Println(err)
 49		return ""
 50	}
 51	return buff.String()
 52}
 53
 54func gmiIndex(w *gmi.ResponseWriter, r *gmi.Request) {
 55	log.Println("Index request")
 56	t, err := template.ParseFiles("templates/index.gmi")
 57	if err != nil {
 58		log.Fatal(err)
 59	}
 60	files, err := getIndexFiles(false)
 61	users, err := getActiveUserNames()
 62	if err != nil {
 63		log.Println(err)
 64		w.WriteHeader(40, "Internal server error")
 65	}
 66	data := struct {
 67		Host      string
 68		SiteTitle string
 69		Files     []*File
 70		Users     []string
 71	}{
 72		Host:      c.Host,
 73		SiteTitle: c.SiteTitle,
 74		Files:     files,
 75		Users:     users,
 76	}
 77	t.Execute(w, data)
 78}
 79
 80func gmiPage(w *gmi.ResponseWriter, r *gmi.Request) {
 81	userName := filepath.Clean(strings.Split(r.URL.Host, ".")[0]) // clean probably unnecessary
 82	fileName := filepath.Clean(r.URL.Path)
 83	if fileName == "/" {
 84		fileName = "index.gmi"
 85	} else if strings.HasPrefix(fileName, "/.hidden") {
 86		w.WriteStatus(gmi.StatusNotFound)
 87		return
 88	}
 89	log.Println("Request for gemini file", fileName, "for user", userName)
 90
 91	if fileName == "/gemlog" { // temp hack
 92		w.SetMediaType("text/gemini")
 93		io.Copy(w, strings.NewReader(generateGemfeedPage(userName)))
 94		return
 95	}
 96
 97	gmi.ServeFile(w, gmi.Dir(path.Join(c.FilesDirectory, userName)), fileName)
 98}
 99
100func runGeminiServer() {
101	log.Println("Starting gemini server")
102	var err error
103	gt, err = template.ParseGlob(path.Join(c.TemplatesDirectory, "*.gmi"))
104	if err != nil {
105		log.Fatal(err)
106	}
107	var server gmi.Server
108	server.ReadTimeout = 1 * time.Minute
109	server.WriteTimeout = 2 * time.Minute
110
111	hostname := strings.SplitN(c.Host, ":", 2)[0]
112	// is this necc?
113	err = server.Certificates.Load(c.GeminiCertStore)
114	if err != nil {
115	}
116	server.CreateCertificate = func(h string) (tls.Certificate, error) {
117		log.Println("Generating certificate for", h)
118		return gmi.CreateCertificate(gmi.CertificateOptions{
119			Subject: pkix.Name{
120				CommonName: hostname,
121			},
122			DNSNames: []string{h},
123			Duration: time.Hour * 760, // one month
124		})
125	}
126
127	var mux gmi.ServeMux
128	// replace with wildcard cert
129	mux.HandleFunc("/", gmiIndex)
130
131	var wildcardMux gmi.ServeMux
132	wildcardMux.HandleFunc("/", gmiPage)
133	server.Register(hostname, &mux)
134	server.Register("*."+hostname, &wildcardMux)
135
136	err = server.ListenAndServe()
137	if err != nil {
138		log.Fatal(err)
139	}
140}