diff --git a/Makefile b/Makefile index 5f2d62e..f1d598f 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,9 @@ run: build run_with_fixed_auth: build ./mycorrhiza -auth-method fixed metarrhiza +run_with_gemini: build + ./mycorrhiza -gemini-cert-path "." metarrhiza + build: go generate go build . diff --git a/flag.go b/flag.go index 47a2134..68e0c42 100644 --- a/flag.go +++ b/flag.go @@ -19,6 +19,7 @@ func init() { flag.StringVar(&util.AuthMethod, "auth-method", "none", "What auth method to use. Variants: \"none\", \"fixed\"") flag.StringVar(&util.FixedCredentialsPath, "fixed-credentials-path", "mycocredentials.json", "Used when -auth-method=fixed. Path to file with user credentials.") flag.StringVar(&util.HeaderLinksHypha, "header-links-hypha", "", "Optional hypha that overrides the header links") + flag.StringVar(&util.GeminiCertPath, "gemini-cert-path", "", "Directory where you store Gemini certificates. Leave empty if you don't want to use Gemini.") } // Do the things related to cli args and die maybe diff --git a/gemini.go b/gemini.go new file mode 100644 index 0000000..3ed4ceb --- /dev/null +++ b/gemini.go @@ -0,0 +1,83 @@ +package main + +import ( + "crypto/tls" + "crypto/x509/pkix" + "io/ioutil" + "log" + "path/filepath" + "time" + + "git.sr.ht/~adnano/go-gemini" + "git.sr.ht/~adnano/go-gemini/certificate" + + "github.com/bouncepaw/mycorrhiza/markup" + "github.com/bouncepaw/mycorrhiza/util" +) + +func geminiHomeHypha(w *gemini.ResponseWriter, rq *gemini.Request) { + log.Println(rq.URL) + w.Write([]byte(`# MycorrhizaWiki + +You have successfully served the wiki through Gemini. Currently, support is really work-in-progress; you should resort to using Mycorrhiza through the web protocols. + +Visit home hypha: +=> /hypha/` + util.HomePage)) +} + +func geminiHypha(w *gemini.ResponseWriter, rq *gemini.Request) { + log.Println(rq.URL) + var ( + hyphaName = geminiHyphaNameFromRq(rq, "page", "hypha") + data, hyphaExists = HyphaStorage[hyphaName] + hasAmnt = hyphaExists && data.BinaryPath != "" + contents string + ) + if hyphaExists { + fileContentsT, errT := ioutil.ReadFile(data.TextPath) + if errT == nil { + md := markup.Doc(hyphaName, string(fileContentsT)) + contents = md.AsGemtext() + } + } + if hasAmnt { + w.Write([]byte("This hypha has an attachment\n")) + } + w.Write([]byte(contents)) +} + +func handleGemini() { + if util.GeminiCertPath == "" { + return + } + certPath, err := filepath.Abs(util.GeminiCertPath) + if err != nil { + log.Fatal(err) + } + + var server gemini.Server + server.ReadTimeout = 30 * time.Second + server.WriteTimeout = 1 * time.Minute + if err := server.Certificates.Load(certPath); err != nil { + log.Fatal(err) + } + server.CreateCertificate = func(hostname string) (tls.Certificate, error) { + return certificate.Create(certificate.CreateOptions{ + Subject: pkix.Name{ + CommonName: hostname, + }, + DNSNames: []string{hostname}, + Duration: 365 * 24 * time.Hour, + }) + } + + var mux gemini.ServeMux + mux.HandleFunc("/", geminiHomeHypha) + mux.HandleFunc("/hypha/", geminiHypha) + mux.HandleFunc("/page/", geminiHypha) + + server.Handle("localhost", &mux) + if err := server.ListenAndServe(); err != nil { + log.Fatal(err) + } +} diff --git a/go.mod b/go.mod index 137cbae..26dbe9f 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module github.com/bouncepaw/mycorrhiza go 1.14 require ( + git.sr.ht/~adnano/go-gemini v0.1.13 github.com/adrg/xdg v0.2.2 github.com/gorilla/feeds v1.1.1 github.com/kr/pretty v0.2.1 // indirect github.com/valyala/quicktemplate v1.6.3 + tildegit.org/solderpunk/gemcert v0.0.0-20200801165357-fc14deb27512 // indirect ) diff --git a/go.sum b/go.sum index f950cb8..0aa3df2 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +git.sr.ht/~adnano/go-gemini v0.1.13 h1:vzKkkVrOzMpfJ1AAeE/PChg0Rw5Zf+9HrnwsgVxXUT4= +git.sr.ht/~adnano/go-gemini v0.1.13/go.mod h1:If1VxEWcZDrRt5FeAFnGTcM2Ud1E3BXs3VJ5rnZWKq0= github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo= github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= @@ -33,3 +35,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +tildegit.org/solderpunk/gemcert v0.0.0-20200801165357-fc14deb27512 h1:reGEt1vmGompn/6FitHdBatILTsK9CYnQOCw3weoW/s= +tildegit.org/solderpunk/gemcert v0.0.0-20200801165357-fc14deb27512/go.mod h1:gqBK7AJ5wPR1bpFOuPmlQObYxwXrFdZmNb2vdzquqoA= diff --git a/main.go b/main.go index 7395dca..d35420e 100644 --- a/main.go +++ b/main.go @@ -193,6 +193,8 @@ func main() { history.Start(WikiDir) setHeaderLinks() + go handleGemini() + // See http_readers.go for /page/, /hypha/, /text/, /binary/ // See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/, /unattach-ask/, /unattach-confirm/ // See http_auth.go for /login, /login-data, /logout, /logout-confirm diff --git a/markup/mycomarkup.go b/markup/mycomarkup.go index f15c07b..2fadd4e 100644 --- a/markup/mycomarkup.go +++ b/markup/mycomarkup.go @@ -48,6 +48,11 @@ func (md *MycoDoc) AsHTML() string { return md.html } +// AsGemtext returns a gemtext representation of the document. Currently really limited, just returns source text +func (md *MycoDoc) AsGemtext() string { + return md.contents +} + // Used to clear opengraph description from html tags. This method is usually bad because of dangers of malformed HTML, but I'm going to use it only for Mycorrhiza-generated HTML, so it's okay. The question mark is required; without it the whole string is eaten away. var htmlTagRe = regexp.MustCompile(`<.*?>`) diff --git a/metarrhiza b/metarrhiza index be5b922..e7040f3 160000 --- a/metarrhiza +++ b/metarrhiza @@ -1 +1 @@ -Subproject commit be5b922e9b564551601d21ed45bf7d9ced65c6bb +Subproject commit e7040f3e0dc41809063b77fcbc12fe33b234ea87 diff --git a/name.go b/name.go index 57c860a..b3a296b 100644 --- a/name.go +++ b/name.go @@ -6,6 +6,8 @@ import ( "net/http" "strings" + "git.sr.ht/~adnano/go-gemini" + "github.com/bouncepaw/mycorrhiza/util" ) @@ -58,3 +60,15 @@ func HyphaNameFromRq(rq *http.Request, actions ...string) string { log.Fatal("HyphaNameFromRq: no matching action passed") return "" } + +// geminiHyphaNameFromRq extracts hypha name from gemini request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha". +func geminiHyphaNameFromRq(rq *gemini.Request, actions ...string) string { + p := rq.URL.Path + for _, action := range actions { + if strings.HasPrefix(p, "/"+action+"/") { + return util.CanonicalName(strings.TrimPrefix(p, "/"+action+"/")) + } + } + log.Fatal("HyphaNameFromRq: no matching action passed") + return "" +} diff --git a/util/util.go b/util/util.go index acf0eec..108fa05 100644 --- a/util/util.go +++ b/util/util.go @@ -19,6 +19,7 @@ var ( HeaderLinksHypha string AuthMethod string FixedCredentialsPath string + GeminiCertPath string ) // ShorterPath is used by handlerList to display shorter path to the files. It simply strips WikiDir.