Add option to allow local file access

This commit is contained in:
Trevor Slocum 2020-11-24 18:18:16 -08:00
parent 11183c0c63
commit e2232a8dc8
5 changed files with 51 additions and 10 deletions

View file

@ -1,5 +1,6 @@
1.0.1: 1.0.1:
- Display navigation bar in pages - Display navigation bar in pages
- Add option to allow local file access
1.0.0: 1.0.0:
- Initial release - Initial release

View file

@ -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 Files `localhost.crt` and `localhost.key` are generated. Rename these files to
match the domain where the certificate will be used. 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 # Example config.yaml
```yaml ```yaml

View file

@ -33,9 +33,11 @@ func openBrowser(url string) {
func main() { func main() {
var view bool var view bool
var allowFile bool
var daemon string var daemon string
var configFile string var configFile string
flag.BoolVar(&view, "view", false, "open web browser") 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(&daemon, "daemon", "", "start daemon on specified address")
flag.StringVar(&configFile, "config", "", "path to configuration file") flag.StringVar(&configFile, "config", "", "path to configuration file")
// TODO option to include response header in page // TODO option to include response header in page
@ -76,7 +78,7 @@ func main() {
} }
if daemon != "" { if daemon != "" {
err := gmitohtml.StartDaemon(daemon) err := gmitohtml.StartDaemon(daemon, allowFile)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View file

@ -22,6 +22,11 @@ func rewriteURL(u string, loc *url.URL) string {
if daemonAddress != "" { if daemonAddress != "" {
if strings.HasPrefix(u, "gemini://") { if strings.HasPrefix(u, "gemini://") {
return "http://" + daemonAddress + "/gemini/" + u[9:] 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, "://") { } else if strings.Contains(u, "://") {
return u return u
} else if loc != nil && len(u) > 0 && !strings.HasPrefix(u, "//") { } else if loc != nil && len(u) > 0 && !strings.HasPrefix(u, "//") {

View file

@ -11,13 +11,17 @@ import (
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
"path"
"strings" "strings"
"time" "time"
) )
var lastRequestTime = time.Now().Unix() 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. // ErrInvalidCertificate is the error returned when an invalid certificate is provided.
var ErrInvalidCertificate = errors.New("invalid certificate") var ErrInvalidCertificate = errors.New("invalid certificate")
@ -165,12 +169,17 @@ func handleRequest(writer http.ResponseWriter, request *http.Request) {
} }
pathSplit := strings.Split(request.URL.Path, "/") 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")) writer.Write([]byte("Error: invalid protocol, only Gemini is supported"))
return 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 { if err != nil {
writer.Write([]byte("Error: invalid URL")) writer.Write([]byte("Error: invalid URL"))
return return
@ -186,11 +195,26 @@ func handleRequest(writer http.ResponseWriter, request *http.Request) {
return return
} }
header, data, err := fetch(u.String()) var header []byte
var data []byte
if scheme == "gemini://" {
header, data, err = fetch(u.String())
if err != nil { if err != nil {
fmt.Fprintf(writer, "Error: failed to fetch %s: %s", u, err) fmt.Fprintf(writer, "Error: failed to fetch %s: %s", u, err)
return 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
}
if len(header) > 0 && header[0] == '3' { if len(header) > 0 && header[0] == '3' {
split := bytes.SplitN(header, []byte(" "), 2) split := bytes.SplitN(header, []byte(" "), 2)
@ -219,10 +243,11 @@ func handleAssets(writer http.ResponseWriter, request *http.Request) {
} }
// StartDaemon starts the page conversion daemon. // StartDaemon starts the page conversion daemon.
func StartDaemon(address string) error { func StartDaemon(address string, allowFile bool) error {
loadAssets()
daemonAddress = address daemonAddress = address
allowFileAccess = allowFile
loadAssets()
handler := http.NewServeMux() handler := http.NewServeMux()
handler.HandleFunc("/assets/style.css", handleAssets) handler.HandleFunc("/assets/style.css", handleAssets)