flounder

A simple gemini site builder
git clone git://git.alexwennerberg.com/flounder
Log | Files | Refs | README | LICENSE

gemini.go (3901B)


      1 package main
      2 
      3 import (
      4 	"bytes"
      5 	"context"
      6 	"io"
      7 	"io/ioutil"
      8 	"log"
      9 	"os"
     10 	"path"
     11 	"strings"
     12 	"text/template"
     13 	"time"
     14 
     15 	gmi "git.sr.ht/~adnano/go-gemini"
     16 	"git.sr.ht/~adnano/go-gemini/certificate"
     17 )
     18 
     19 var gt *template.Template
     20 
     21 func 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 
     33 func 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 
     55 func gmiIndex(w gmi.ResponseWriter, r *gmi.Request) {
     56 	t, err := template.ParseFiles("templates/index.gmi")
     57 	if err != nil {
     58 		log.Fatal(err)
     59 	}
     60 	files, err := getUpdatedFiles(false, "")
     61 	users, err := getActiveUserNames()
     62 	if err != nil {
     63 		log.Println(err)
     64 		w.WriteHeader(gmi.StatusTemporaryFailure, "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 
     80 func gmiPage(_ context.Context, w gmi.ResponseWriter, r *gmi.Request) {
     81 	// redundant
     82 	hostname := strings.SplitN(c.Host, ":", 2)[0]
     83 	if r.URL.Host == hostname {
     84 		gmiIndex(w, r)
     85 		return
     86 	}
     87 	var userName string
     88 	custom := domains[r.URL.Host]
     89 	if custom != "" {
     90 		userName = custom
     91 	} else {
     92 		userName = cleanPath(strings.Split(r.URL.Host, ".")[0])[1:] // clean probably unnecessary
     93 	}
     94 	fileName := cleanPath(r.URL.Path)
     95 	if strings.HasPrefix(fileName, "/"+HiddenFolder) {
     96 		w.WriteHeader(gmi.StatusNotFound, "Not found")
     97 		return
     98 	}
     99 	fullPath := path.Join(c.FilesDirectory, userName, fileName)
    100 	if fileName == "/gemlog" { // temp hack
    101 		_, err := os.Stat(path.Join(fullPath, "index.gmi"))
    102 		if err != nil {
    103 			w.WriteHeader(gmi.StatusSuccess, "text/gemini")
    104 			io.Copy(w, strings.NewReader(generateGemfeedPage(userName)))
    105 			return
    106 		}
    107 	} else if fileName == "/gemlog/atom.xml" {
    108 		_, err := os.Stat(fullPath)
    109 		if err != nil {
    110 			w.WriteHeader(gmi.StatusSuccess, "application/atom+xml")
    111 			feed := generateFeedFromUser(userName)
    112 			atomString := feed.toAtomFeed()
    113 			io.Copy(w, strings.NewReader(atomString))
    114 			return
    115 		}
    116 	}
    117 	if fileName == "" { // mess
    118 		fileName = "/"
    119 	}
    120 
    121 	gmi.ServeFile(w, os.DirFS(path.Join(c.FilesDirectory, userName)), fileName)
    122 }
    123 
    124 var certificates = certificate.Store{}
    125 
    126 func runGeminiServer() {
    127 	log.Println("Starting gemini server")
    128 	var err error
    129 	gt = template.New("main").Funcs(template.FuncMap{
    130 		"urlencode": func(s string) string {
    131 			// Only need to escape spaces to make gemini links not break
    132 			return strings.Replace(s, " ", "%20", -1)
    133 		},
    134 	})
    135 	gt, err = gt.ParseGlob(path.Join(c.TemplatesDirectory, "*.gmi"))
    136 	if err != nil {
    137 		log.Fatal(err)
    138 	}
    139 	var server gmi.Server
    140 	server.ReadTimeout = 1 * time.Minute
    141 	server.WriteTimeout = 2 * time.Minute
    142 
    143 	hostname := strings.SplitN(c.Host, ":", 2)[0]
    144 	// is this necc?
    145 	err = certificates.Load(c.GeminiCertStore)
    146 	if err != nil {
    147 		log.Fatal(err)
    148 	}
    149 	certificates.Register("*." + hostname)
    150 	certificates.Register(hostname)
    151 	for k, _ := range domains {
    152 		certificates.Register(k)
    153 	}
    154 	server.GetCertificate = certificates.Get
    155 
    156 	var mux gmi.Mux
    157 
    158 	mux.HandleFunc("/", gmiPage)
    159 	server.Handler = gmi.LoggingMiddleware(&mux)
    160 
    161 	err = server.ListenAndServe(context.Background())
    162 	if err != nil {
    163 		log.Fatal(err)
    164 	}
    165 }