flounder

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

gemini.go (4057B) - raw


      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 	// hack
    101 	if c.Hack && userName == "lists" {
    102 		if fileName == "" { // mess
    103 			fileName = "/"
    104 		}
    105 		gmi.ServeFile(w, os.DirFS("/www/crabmail", fileName))
    106 	}
    107 	if fileName == "/gemlog" { // temp hack
    108 		_, err := os.Stat(path.Join(fullPath, "index.gmi"))
    109 		if err != nil {
    110 			w.WriteHeader(gmi.StatusSuccess, "text/gemini")
    111 			io.Copy(w, strings.NewReader(generateGemfeedPage(userName)))
    112 			return
    113 		}
    114 	} else if fileName == "/gemlog/atom.xml" {
    115 		_, err := os.Stat(fullPath)
    116 		if err != nil {
    117 			w.WriteHeader(gmi.StatusSuccess, "application/atom+xml")
    118 			feed := generateFeedFromUser(userName)
    119 			atomString := feed.toAtomFeed()
    120 			io.Copy(w, strings.NewReader(atomString))
    121 			return
    122 		}
    123 	}
    124 	if fileName == "" { // mess
    125 		fileName = "/"
    126 	}
    127 
    128 	gmi.ServeFile(w, os.DirFS(path.Join(c.FilesDirectory, userName)), fileName)
    129 }
    130 
    131 var certificates = certificate.Store{}
    132 
    133 func runGeminiServer() {
    134 	log.Println("Starting gemini server")
    135 	var err error
    136 	gt = template.New("main").Funcs(template.FuncMap{
    137 		"urlencode": func(s string) string {
    138 			// Only need to escape spaces to make gemini links not break
    139 			return strings.Replace(s, " ", "%20", -1)
    140 		},
    141 	})
    142 	gt, err = gt.ParseGlob(path.Join(c.TemplatesDirectory, "*.gmi"))
    143 	if err != nil {
    144 		log.Fatal(err)
    145 	}
    146 	var server gmi.Server
    147 	server.ReadTimeout = 1 * time.Minute
    148 	server.WriteTimeout = 2 * time.Minute
    149 
    150 	hostname := strings.SplitN(c.Host, ":", 2)[0]
    151 	// is this necc?
    152 	err = certificates.Load(c.GeminiCertStore)
    153 	if err != nil {
    154 		log.Fatal(err)
    155 	}
    156 	certificates.Register("*." + hostname)
    157 	certificates.Register(hostname)
    158 	for k, _ := range domains {
    159 		certificates.Register(k)
    160 	}
    161 	server.GetCertificate = certificates.Get
    162 
    163 	var mux gmi.Mux
    164 
    165 	mux.HandleFunc("/", gmiPage)
    166 	server.Handler = gmi.LoggingMiddleware(&mux)
    167 
    168 	err = server.ListenAndServe(context.Background())
    169 	if err != nil {
    170 		log.Fatal(err)
    171 	}
    172 }