Support directory listing via HTTPS

This commit is contained in:
Trevor Slocum 2020-12-03 16:45:20 -08:00
parent 8d6cb6527e
commit faab032d45
4 changed files with 91 additions and 72 deletions

View File

@ -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
```

View File

@ -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
}

View File

@ -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)

View File

@ -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()