|
|
|
@ -12,6 +12,8 @@ import (
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"path"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"sort"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
@ -48,7 +50,66 @@ func writeStatus(c net.Conn, code int) {
|
|
|
|
|
writeHeader(c, code, meta)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func serveFile(c net.Conn, requestData, filePath string) {
|
|
|
|
|
func serveDirectory(c net.Conn, request *url.URL, dirPath string) {
|
|
|
|
|
var files []os.FileInfo
|
|
|
|
|
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
} else if path == dirPath {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
files = append(files, info)
|
|
|
|
|
if info.IsDir() {
|
|
|
|
|
return filepath.SkipDir
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeStatus(c, gemini.StatusTemporaryFailure)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// List directories first
|
|
|
|
|
sort.Slice(files, func(i, j int) bool {
|
|
|
|
|
iDir := files[i].IsDir() || files[i].Mode()&os.ModeSymlink != 0
|
|
|
|
|
jDir := files[j].IsDir() || files[j].Mode()&os.ModeSymlink != 0
|
|
|
|
|
if iDir != jDir {
|
|
|
|
|
return iDir
|
|
|
|
|
}
|
|
|
|
|
return i < j
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
writeHeader(c, gemini.StatusSuccess, "text/gemini; charset=utf-8")
|
|
|
|
|
|
|
|
|
|
c.Write([]byte("# " + request.Path + "\r\n\r\n"))
|
|
|
|
|
|
|
|
|
|
if request.Path != "/" {
|
|
|
|
|
c.Write([]byte("=> ../ ../\r\n\r\n"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, info := range files {
|
|
|
|
|
fileName := info.Name()
|
|
|
|
|
filePath := url.PathEscape(info.Name())
|
|
|
|
|
if info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
|
|
|
|
|
fileName += "/"
|
|
|
|
|
filePath += "/"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.Write([]byte("=> " + fileName + " " + filePath + "\r\n"))
|
|
|
|
|
|
|
|
|
|
if info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
|
|
|
|
|
c.Write([]byte("\r\n"))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
modified := "Never"
|
|
|
|
|
if !info.ModTime().IsZero() {
|
|
|
|
|
modified = info.ModTime().Format("2006-01-02 3:04 PM")
|
|
|
|
|
}
|
|
|
|
|
c.Write([]byte(modified + " - " + formatFileSize(info.Size()) + "\r\n\r\n"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func serveFile(c net.Conn, request *url.URL, requestData, filePath string, listDir bool) {
|
|
|
|
|
fi, err := os.Stat(filePath)
|
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
|
writeStatus(c, gemini.StatusNotFound)
|
|
|
|
@ -58,6 +119,9 @@ func serveFile(c net.Conn, requestData, filePath string) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
originalPath := filePath
|
|
|
|
|
|
|
|
|
|
var fetchIndex bool
|
|
|
|
|
if mode := fi.Mode(); mode.IsDir() {
|
|
|
|
|
if requestData[len(requestData)-1] != '/' {
|
|
|
|
|
// Add trailing slash
|
|
|
|
@ -66,6 +130,8 @@ func serveFile(c net.Conn, requestData, filePath string) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fetchIndex = true
|
|
|
|
|
|
|
|
|
|
_, err := os.Stat(path.Join(filePath, "index.gemini"))
|
|
|
|
|
if err == nil {
|
|
|
|
|
filePath = path.Join(filePath, "index.gemini")
|
|
|
|
@ -76,6 +142,10 @@ func serveFile(c net.Conn, requestData, filePath string) {
|
|
|
|
|
|
|
|
|
|
fi, err = os.Stat(filePath)
|
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
|
if fetchIndex && listDir {
|
|
|
|
|
serveDirectory(c, request, originalPath)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
writeStatus(c, gemini.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
} else if err != nil {
|
|
|
|
@ -268,29 +338,29 @@ func handleConn(c net.Conn) {
|
|
|
|
|
matchedHost = true
|
|
|
|
|
|
|
|
|
|
for _, serve := range config.Hosts[hostname] {
|
|
|
|
|
if serve.Proxy != "" {
|
|
|
|
|
if serve.r != nil && serve.r.Match(pathBytes) {
|
|
|
|
|
if serve.r != nil && serve.r.Match(pathBytes) {
|
|
|
|
|
if serve.Proxy != "" {
|
|
|
|
|
serveProxy(c, requestData, serve.Proxy)
|
|
|
|
|
return
|
|
|
|
|
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) {
|
|
|
|
|
serveProxy(c, requestData, serve.Proxy)
|
|
|
|
|
} else if serve.cmd != nil {
|
|
|
|
|
serveCommand(c, serve.cmd)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
} else if serve.cmd != nil {
|
|
|
|
|
if serve.r != nil && serve.r.Match(pathBytes) {
|
|
|
|
|
serveCommand(c, serve.cmd)
|
|
|
|
|
serveFile(c, request, requestData, path.Join(serve.Root, strippedPath), serve.ListDirectory)
|
|
|
|
|
return
|
|
|
|
|
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) {
|
|
|
|
|
if serve.Proxy != "" {
|
|
|
|
|
serveProxy(c, requestData, serve.Proxy)
|
|
|
|
|
return
|
|
|
|
|
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) {
|
|
|
|
|
} else if serve.cmd != nil {
|
|
|
|
|
serveCommand(c, serve.cmd)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if serve.r != nil && serve.r.Match(pathBytes) {
|
|
|
|
|
serveFile(c, requestData, path.Join(serve.Root, strippedPath))
|
|
|
|
|
return
|
|
|
|
|
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) {
|
|
|
|
|
serveFile(c, requestData, path.Join(serve.Root, strippedPath[len(serve.Path)-1:]))
|
|
|
|
|
filePath := request.Path[len(serve.Path):]
|
|
|
|
|
if len(filePath) > 0 && filePath[0] == '/' {
|
|
|
|
|
filePath = filePath[1:]
|
|
|
|
|
}
|
|
|
|
|
serveFile(c, request, requestData, path.Join(serve.Root, filePath), serve.ListDirectory)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|