flounder

A simple gemini site builder
Log | Files | Refs | README | LICENSE

commit 5c687e0a49cd53db58b2a13728594c7460000443
parent 1fab64591b2c09823f1ef7fb24e713d7bf7488d4
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Sat, 24 Oct 2020 01:13:39 -0700

a bunch of misc changes

Diffstat:
Mflounder.toml | 3+++
Mgemini.go | 20+++++++++++++-------
Mgo.mod | 2++
Mgo.sum | 6++++++
Mhttp.go | 64+++++++++++++++++++++++++++++++++-------------------------------
Mmain.go | 16+++++++++++++---
Mtemplates/index.gmi | 2+-
Mtemplates/static/style.css | 5-----
Atemplates/user_page.html | 20++++++++++++++++++++
9 files changed, 91 insertions(+), 47 deletions(-)

diff --git a/flounder.toml b/flounder.toml @@ -1,3 +1,4 @@ +# Used in HTML templates and titles SiteTitle="­čÉčflounder" RootDomain="localhost" FilesDirectory="./files" @@ -5,3 +6,5 @@ FilesDirectory="./files" # everything in the static subfolder will be served at root TemplatesDirectory="./templates" DBFile="./flounder.db" + +# Log file diff --git a/gemini.go b/gemini.go @@ -12,6 +12,7 @@ import ( "log" "os" "path" + "path/filepath" "text/template" "time" ) @@ -24,21 +25,24 @@ func gmiIndex(w *gmi.ResponseWriter, r *gmi.Request) { files, _ := getIndexFiles() users, _ := getUsers() data := struct { - Domain string - Files []*File - Users []string + Domain string + SiteTitle string + Files []*File + Users []string }{ - Domain: "flounder.online", - Files: files, - Users: users, + Domain: c.RootDomain, + SiteTitle: c.SiteTitle, + Files: files, + Users: users, } t.Execute(w, data) } func gmiPage(w *gmi.ResponseWriter, r *gmi.Request) { userName := strings.Split(r.URL.Host, ".")[0] - fileName := path.Join(c.FilesDirectory, userName, r.URL.Path) + fileName := path.Join(c.FilesDirectory, userName, filepath.Clean(r.URL.Path)) data, err := ioutil.ReadFile(fileName) + // serve file? // TODO write mimetype if err != nil { // TODO return 404 equivalent @@ -92,6 +96,8 @@ func runGeminiServer() { server.ListenAndServe() } +// TODO log request + // writeCertificate writes the provided certificate and private key // to path.crt and path.key respectively. func writeCertificate(path string, cert tls.Certificate) error { diff --git a/go.mod b/go.mod @@ -5,5 +5,7 @@ go 1.15 require ( git.sr.ht/~adnano/gmi v0.1.0-alpha.2 github.com/BurntSushi/toml v0.3.1 + github.com/gorilla/handlers v1.5.1 golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e ) diff --git a/go.sum b/go.sum @@ -2,6 +2,10 @@ git.sr.ht/~adnano/gmi v0.1.0-alpha.2 h1:5/wzImYT3mJmZ27lazJ8YAdpiVN3QNJruLX7PXOI git.sr.ht/~adnano/gmi v0.1.0-alpha.2/go.mod h1:t/m2KtH+7lXIF7jjVN+bNvwPbE0nxHOpvlA/WZ/KeLQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -9,3 +13,5 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/http.go b/http.go @@ -2,12 +2,15 @@ package main import ( "git.sr.ht/~adnano/gmi" + "github.com/gorilla/handlers" "html/template" "log" "net/http" "os" "path" + "path/filepath" "strings" + "time" ) var t *template.Template @@ -26,7 +29,7 @@ func renderError(w http.ResponseWriter, errorMsg string, statusCode int) { // TO func rootHandler(w http.ResponseWriter, r *http.Request) { // serve everything inside static directory if r.URL.Path != "/" { - fileName := path.Join(c.TemplatesDirectory, "static", r.URL.Path) + fileName := path.Join(c.TemplatesDirectory, "static", filepath.Clean(r.URL.Path)) http.ServeFile(w, r, fileName) return } @@ -154,25 +157,19 @@ func registerHandler(w http.ResponseWriter, r *http.Request) { // Server a user's file func userFile(w http.ResponseWriter, r *http.Request) { userName := strings.Split(r.Host, ".")[0] - fileName := path.Join(c.FilesDirectory, userName, r.URL.Path) + fileName := path.Join(c.FilesDirectory, userName, filepath.Clean(r.URL.Path)) extension := path.Ext(fileName) if r.URL.Path == "/static/style.css" { http.ServeFile(w, r, path.Join(c.TemplatesDirectory, "static/style.css")) } if extension == ".gmi" || extension == ".gemini" { - if strings.Contains(fileName, "..") { - // prevent directory traversal TODO verify - http.Error(w, "invalid URL path", http.StatusBadRequest) - } else { - // covert to html - stat, _ := os.Stat(fileName) - file, _ := os.Open(fileName) - htmlString := gmi.Parse(file).HTML() - reader := strings.NewReader(htmlString) - w.Header().Set("Content-Type", "text/html") - http.ServeContent(w, r, fileName, stat.ModTime(), reader) - } - // TODO clean + // covert to html + stat, _ := os.Stat(fileName) + file, _ := os.Open(fileName) + htmlString := gmi.Parse(file).HTML() + reader := strings.NewReader(htmlString) + w.Header().Set("Content-Type", "text/html") + http.ServeContent(w, r, fileName, stat.ModTime(), reader) } else { http.ServeFile(w, r, fileName) } @@ -185,22 +182,27 @@ func runHTTPServer() { if err != nil { log.Fatal(err) } - http.HandleFunc(c.RootDomain+"/", rootHandler) - http.HandleFunc(c.RootDomain+"/my_site", mySiteHandler) - http.HandleFunc(c.RootDomain+"/edit/", editFileHandler) - http.HandleFunc(c.RootDomain+"/login", loginHandler) - http.HandleFunc(c.RootDomain+"/register", registerHandler) - http.HandleFunc(c.RootDomain+"/delete/", deleteFileHandler) - // login+register functions + serveMux := http.NewServeMux() - // handle user files based on subdomain - http.HandleFunc("/", userFile) - log.Fatal(http.ListenAndServe(":8080", logRequest(http.DefaultServeMux))) -} + serveMux.HandleFunc(c.RootDomain+"/", rootHandler) + serveMux.HandleFunc(c.RootDomain+"/my_site", mySiteHandler) + serveMux.HandleFunc(c.RootDomain+"/edit/", editFileHandler) + serveMux.HandleFunc(c.RootDomain+"/login", loginHandler) + serveMux.HandleFunc(c.RootDomain+"/register", registerHandler) + serveMux.HandleFunc(c.RootDomain+"/delete/", deleteFileHandler) -func logRequest(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL) - handler.ServeHTTP(w, r) - }) + wrapped := handlers.LoggingHandler(os.Stdout, serveMux) + + // handle user files based on subdomain + serveMux.HandleFunc("/", userFile) + // login+register functions + srv := &http.Server{ + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 120 * time.Second, + Addr: ":8080", + // TLSConfig: tlsConfig, + Handler: wrapped, + } + log.Fatal(srv.ListenAndServe()) } diff --git a/main.go b/main.go @@ -7,6 +7,7 @@ import ( "os" "path" "path/filepath" + "sync" ) var c Config // global var to hold static configuration @@ -71,7 +72,16 @@ func main() { if err != nil { log.Fatal(err) } - runHTTPServer() - // runGeminiServer() - // go log.Fatal(gmi.ListenAndServe(":8080", nil)) + + wg := new(sync.WaitGroup) + wg.Add(2) + go func() { + runHTTPServer() + wg.Done() + }() + go func() { + runGeminiServer() + wg.Done() + }() + wg.Wait() } diff --git a/templates/index.gmi b/templates/index.gmi @@ -1,5 +1,5 @@ {{$domain := .Domain}} -# ­čÉčFlounder! +# {{.SiteTitle}}! Welcome to flounder, a home for Gemini sites. Flounder hosts small Gemini web pages over https and Gemini. Right now, the only way to make an account is via the https portal, but I'm working on adding alternatives. Feel free to make an account and join if you'd like! diff --git a/templates/static/style.css b/templates/static/style.css @@ -4,14 +4,9 @@ main { margin: auto; font-family: Helvetica, Arial, monospace; word-wrap: break-word; - border: 1px solid black; background-color: white; } -body { - background-color: #cfe6fc; -} - .inline { display: inline; } diff --git a/templates/user_page.html b/templates/user_page.html @@ -0,0 +1,20 @@ +{{$domain := .Domain}} +{{template "header" .}} +<h1>{{.PageTitle}}!</h1> +{{template "nav.html" .}} +<h2>All users:</h2> +{{ range .Users}} +<a href="https://{{.}}.{{$domain}}" class='person-link'>{{.}}</a> +{{end}} +<h2>Recently updated files:</h2> +{{ range .Files }} +<div> + <a href="https://{{.Creator}}.{{$domain}}" class='person-link'> + {{ .Creator }}</a> + <em>{{.UpdatedTime}}</em> + <a href="https://{{.Creator}}.{{$domain}}/{{.Name}}"> + {{ .Name}} + </a> +</div> +{{end}} +{{template "footer" .}}