From e2232a8dc8f551c108a8c12a2dce0dff3106f372 Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Tue, 24 Nov 2020 18:18:16 -0800 Subject: [PATCH] Add option to allow local file access --- CHANGELOG | 1 + CONFIGURATION.md | 8 ++++++++ main.go | 4 +++- pkg/gmitohtml/convert.go | 5 +++++ pkg/gmitohtml/daemon.go | 43 +++++++++++++++++++++++++++++++--------- 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 44587e4..389a39f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ 1.0.1: - Display navigation bar in pages +- Add option to allow local file access 1.0.0: - Initial release diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 33ab118..19139c7 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -19,6 +19,14 @@ openssl req -x509 -out localhost.crt -keyout localhost.key \ Files `localhost.crt` and `localhost.key` are generated. Rename these files to match the domain where the certificate will be used. +## Allow file:// access + +By default, local files are not served by gmitohtml. When executed with the +`--allow-file` argument, local files may be accessed via `file://`. + +For example, to view `/home/dioscuri/sites/gemlog/index.gmi`, navigate to +`file:///home/dioscuri/sites/gemlog/index.gmi`. + # Example config.yaml ```yaml diff --git a/main.go b/main.go index f5eb093..da503f0 100644 --- a/main.go +++ b/main.go @@ -33,9 +33,11 @@ func openBrowser(url string) { func main() { var view bool + var allowFile bool var daemon string var configFile string flag.BoolVar(&view, "view", false, "open web browser") + flag.BoolVar(&allowFile, "allow-file", false, "allow local file access via file://") flag.StringVar(&daemon, "daemon", "", "start daemon on specified address") flag.StringVar(&configFile, "config", "", "path to configuration file") // TODO option to include response header in page @@ -76,7 +78,7 @@ func main() { } if daemon != "" { - err := gmitohtml.StartDaemon(daemon) + err := gmitohtml.StartDaemon(daemon, allowFile) if err != nil { log.Fatal(err) } diff --git a/pkg/gmitohtml/convert.go b/pkg/gmitohtml/convert.go index 7b35753..819678d 100644 --- a/pkg/gmitohtml/convert.go +++ b/pkg/gmitohtml/convert.go @@ -22,6 +22,11 @@ func rewriteURL(u string, loc *url.URL) string { if daemonAddress != "" { if strings.HasPrefix(u, "gemini://") { return "http://" + daemonAddress + "/gemini/" + u[9:] + } else if strings.HasPrefix(u, "file://") { + if !allowFileAccess { + return "http://" + daemonAddress + "/?FileAccessNotAllowed" + } + return "http://" + daemonAddress + "/file/" + u[7:] } else if strings.Contains(u, "://") { return u } else if loc != nil && len(u) > 0 && !strings.HasPrefix(u, "//") { diff --git a/pkg/gmitohtml/daemon.go b/pkg/gmitohtml/daemon.go index abad18b..ef2b665 100644 --- a/pkg/gmitohtml/daemon.go +++ b/pkg/gmitohtml/daemon.go @@ -11,13 +11,17 @@ import ( "log" "net/http" "net/url" + "path" "strings" "time" ) var lastRequestTime = time.Now().Unix() -var clientCerts = make(map[string]tls.Certificate) +var ( + clientCerts = make(map[string]tls.Certificate) + allowFileAccess bool +) // ErrInvalidCertificate is the error returned when an invalid certificate is provided. var ErrInvalidCertificate = errors.New("invalid certificate") @@ -165,12 +169,17 @@ func handleRequest(writer http.ResponseWriter, request *http.Request) { } pathSplit := strings.Split(request.URL.Path, "/") - if len(pathSplit) < 2 || pathSplit[1] != "gemini" { + if len(pathSplit) < 2 || (pathSplit[1] != "gemini" && (!allowFileAccess || pathSplit[1] != "file")) { writer.Write([]byte("Error: invalid protocol, only Gemini is supported")) return } - u, err := url.ParseRequestURI("gemini://" + strings.Join(pathSplit[2:], "/")) + scheme := "gemini://" + if pathSplit[1] == "file" { + scheme = "file://" + } + + u, err := url.ParseRequestURI(scheme + strings.Join(pathSplit[2:], "/")) if err != nil { writer.Write([]byte("Error: invalid URL")) return @@ -186,9 +195,24 @@ func handleRequest(writer http.ResponseWriter, request *http.Request) { return } - header, data, err := fetch(u.String()) - if err != nil { - fmt.Fprintf(writer, "Error: failed to fetch %s: %s", u, err) + var header []byte + var data []byte + if scheme == "gemini://" { + header, data, err = fetch(u.String()) + if err != nil { + fmt.Fprintf(writer, "Error: failed to fetch %s: %s", u, err) + return + } + } else if allowFileAccess && scheme == "file://" { + header = []byte("20 text/gemini; charset=utf-8") + data, err = ioutil.ReadFile(path.Join("/", strings.Join(pathSplit[2:], "/"))) + if err != nil { + fmt.Fprintf(writer, "Error: failed to read file %s: %s", u, err) + return + } + data = Convert(data, u.String()) + } else { + writer.Write([]byte("Error: invalid URL")) return } @@ -219,10 +243,11 @@ func handleAssets(writer http.ResponseWriter, request *http.Request) { } // StartDaemon starts the page conversion daemon. -func StartDaemon(address string) error { - loadAssets() - +func StartDaemon(address string, allowFile bool) error { daemonAddress = address + allowFileAccess = allowFile + + loadAssets() handler := http.NewServeMux() handler.HandleFunc("/assets/style.css", handleAssets)