From faab032d4509cc519a9c0530837b02584215d6ac Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Thu, 3 Dec 2020 16:45:20 -0800 Subject: [PATCH] Support directory listing via HTTPS --- CONFIGURATION.md | 29 +++++++++++++--------------- serve_file.go | 42 ++++++++++++++++++++++++++--------------- serve_https.go | 49 +++++++++++++++++++++++++++++++++++------------- server.go | 43 +++++++++++++++--------------------------- 4 files changed, 91 insertions(+), 72 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 0abb53c..7713229 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -219,17 +219,17 @@ hosts: key: /srv/gemini.rocks/data/cert.key paths: - - path: ^/sites/.*\.php$ - root: /home/geminirocks/data + path: ^/.*\.php$ + root: /home/geminirocks/public_html fastcgi: unix:///var/run/php.sock - - path: /sites - root: /home/geminirocks/data + path: /files + root: /home/geminirocks/files cache: 604800 # Cache for 1 week list: true # Enable directory listing - path: ^/(help|info)$ - root: /home/geminirocks/data/help + root: /home/geminirocks/docs/help - path: ^/proxy-example$ proxy: gemini://localhost:1966 @@ -237,6 +237,13 @@ hosts: path: ^/cmd-example$ command: uname -a cache: 0 # Do not cache + - + path: / + root: /home/geminirocks/public_html + twins.rocketnine.space: + cert: /srv/twins.rocketnine.space/data/cert.crt + key: /srv/twins.rocketnine.space/data/cert.key + paths: - path: /redir-path-example redirect: /other-resource @@ -245,15 +252,5 @@ hosts: redirect: gemini://gemini.circumlunar.space/ - path: / - root: /home/geminirocks/data/home - twins.rocketnine.space: - cert: /srv/twins.rocketnine.space/data/cert.crt - key: /srv/twins.rocketnine.space/data/cert.key - paths: - - - path: /sites - root: /home/twins/data/sites - - - path: / - root: /home/twins/data/home + root: /home/twins/public_html ``` diff --git a/serve_file.go b/serve_file.go index 9dd738b..dc2bf55 100644 --- a/serve_file.go +++ b/serve_file.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "io" "net" @@ -12,8 +13,9 @@ import ( "github.com/h2non/filetype" ) -func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath string) int { +func buildDirList(request *url.URL, dirPath string) ([]byte, error) { var ( + b = &bytes.Buffer{} files []os.FileInfo numDirs int numFiles int @@ -36,7 +38,7 @@ func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath strin return nil }) if err != nil { - return writeStatus(c, statusTemporaryFailure) + return nil, err } // List directories first sort.Slice(files, func(i, j int) bool { @@ -48,24 +50,22 @@ func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath strin return i < j }) - writeSuccess(c, serve, geminiType, -1) - - fmt.Fprintf(c, "# %s%s", request.Path, newLine) + fmt.Fprintf(b, "# %s%s", request.Path, newLine) if numDirs == 1 { - c.Write([]byte("1 directory")) + b.Write([]byte("1 directory")) } else { - fmt.Fprintf(c, "%d directories", numDirs) + fmt.Fprintf(b, "%d directories", numDirs) } - c.Write([]byte(", ")) + b.Write([]byte(", ")) if numDirs == 1 { - c.Write([]byte("1 file")) + b.Write([]byte("1 file")) } else { - fmt.Fprintf(c, "%d files", numFiles) + fmt.Fprintf(b, "%d files", numFiles) } - c.Write([]byte(newLine + newLine)) + b.Write([]byte(newLine + newLine)) if request.Path != "/" { - c.Write([]byte("=> ../ ../" + newLine + newLine)) + b.Write([]byte("=> ../ ../" + newLine + newLine)) } for _, info := range files { @@ -76,10 +76,10 @@ func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath strin filePath += "/" } - c.Write([]byte("=> " + fileName + " " + filePath + newLine)) + b.Write([]byte("=> " + fileName + " " + filePath + newLine)) if info.IsDir() || info.Mode()&os.ModeSymlink != 0 { - c.Write([]byte(newLine)) + b.Write([]byte(newLine)) continue } @@ -87,8 +87,20 @@ func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath strin if !info.ModTime().IsZero() { modified = info.ModTime().Format("2006-01-02 3:04 PM") } - c.Write([]byte(modified + " - " + formatFileSize(info.Size()) + newLine + newLine)) + b.Write([]byte(modified + " - " + formatFileSize(info.Size()) + newLine + newLine)) } + + return b.Bytes(), nil +} + +func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath string) int { + dirList, err := buildDirList(request, dirPath) + if err != nil { + return writeStatus(c, statusTemporaryFailure) + } + + writeSuccess(c, serve, geminiType, -1) + c.Write(dirList) return statusSuccess } diff --git a/serve_https.go b/serve_https.go index 48cbffc..5ce2f96 100644 --- a/serve_https.go +++ b/serve_https.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "path" + "path/filepath" "strings" "gitlab.com/tslocum/gmitohtml/pkg/gmitohtml" @@ -114,22 +115,34 @@ func serveHTTPS(w http.ResponseWriter, r *http.Request) (int, int64, string) { return status, -1, serve.Log } - _, err := os.Stat(path.Join(filePath, "index.gmi")) - if err != nil { - _, err := os.Stat(path.Join(filePath, "index.gemini")) - if err != nil { - if serve.List { + var found bool + for _, indexFile := range indexFiles { + _, err := os.Stat(path.Join(filePath, indexFile)) + if err == nil || os.IsExist(err) { + filePath = path.Join(filePath, indexFile) + found = true + break + } + } + if !found { + if serve.List { + dirList, err := buildDirList(r.URL, filePath) + if err != nil { status := http.StatusInternalServerError - http.Error(w, "HTTPS dir lost not yet implemented", status) + http.Error(w, "Failed to build directory listing", status) return status, -1, serve.Log } + result := gmitohtml.Convert([]byte(dirList), r.URL.String()) - http.NotFound(w, r) - return http.StatusNotFound, -1, serve.Log + status := http.StatusOK + w.Header().Set("Content-Type", htmlType) + w.WriteHeader(status) + + w.Write(result) + return status, int64(len(result)), serve.Log } - filePath = path.Join(filePath, "index.gemini") - } else { - filePath = path.Join(filePath, "index.gmi") + http.NotFound(w, r) + return http.StatusNotFound, -1, serve.Log } } else if hasTrailingSlash && len(r.URL.Path) > 1 { u, err := url.Parse(r.URL.String()) @@ -152,10 +165,20 @@ func serveHTTPS(w http.ResponseWriter, r *http.Request) (int, int64, string) { return status, -1, serve.Log } - result := gmitohtml.Convert([]byte(data), r.URL.String()) + var result []byte + contentType := htmlType + fileExt := strings.ToLower(filepath.Ext(filePath)) + if fileExt == ".gmi" || fileExt == ".gemini" { + result = gmitohtml.Convert([]byte(data), r.URL.String()) + } else if fileExt == ".htm" || fileExt == ".html" { + result = data + } else { + result = data + contentType = plainType + } status := http.StatusOK - w.Header().Set("Content-Type", htmlType) + w.Header().Set("Content-Type", contentType) w.WriteHeader(status) w.Write(result) diff --git a/server.go b/server.go index 2d58738..2e654b7 100644 --- a/server.go +++ b/server.go @@ -2,7 +2,6 @@ package main import ( "bufio" - "bytes" "crypto/tls" "crypto/x509" "fmt" @@ -61,6 +60,8 @@ var newLine = "\r\n" var logLock sync.Mutex +var indexFiles = []string{"index.gmi", "index.gemini"} + func writeHeader(c net.Conn, code int, meta string) int { fmt.Fprintf(c, "%d %s%s", code, meta, newLine) return code @@ -110,22 +111,6 @@ func writeSuccess(c net.Conn, serve *pathConfig, contentType string, size int64) return statusSuccess } -func scanCRLF(data []byte, atEOF bool) (advance int, token []byte, err error) { - if atEOF && len(data) == 0 { - return 0, nil, nil - } - if i := bytes.IndexByte(data, '\r'); i >= 0 { - // We have a full newline-terminated line. - return i + 1, data[0:i], nil - } - // If we're at EOF, we have a final, non-terminated line. Return it. - if atEOF { - return len(data), data, nil - } - // Request more data. - return 0, nil, nil -} - func replaceWithUserInput(command []string, request *url.URL) []string { newCommand := make([]string, len(command)) copy(newCommand, command) @@ -229,18 +214,20 @@ func servePath(c *tls.Conn, request *url.URL, serve *pathConfig) (int, int64) { return writeHeader(c, statusRedirectPermanent, request.String()+"/"), -1 } - _, err := os.Stat(path.Join(filePath, "index.gmi")) - if err != nil { - _, err := os.Stat(path.Join(filePath, "index.gemini")) - if err != nil { - if serve.List { - return serveDirList(c, serve, request, filePath), -1 - } - return writeStatus(c, statusNotFound), -1 + var found bool + for _, indexFile := range indexFiles { + _, err := os.Stat(path.Join(filePath, indexFile)) + if err == nil || os.IsExist(err) { + filePath = path.Join(filePath, indexFile) + found = true + break } - filePath = path.Join(filePath, "index.gemini") - } else { - filePath = path.Join(filePath, "index.gmi") + } + if !found { + if serve.List { + return serveDirList(c, serve, request, filePath), -1 + } + return writeStatus(c, statusNotFound), -1 } } else if hasTrailingSlash && len(request.Path) > 1 { r := request.String()