all repos — flounder @ cbae24f0aacf66aa21c78303bf8f04cf9c05e251

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