mirror of
https://code.rocketnine.space/tslocum/twins.git
synced 2024-11-27 07:38:13 +01:00
parent
d1f9c6d4aa
commit
e7cebf095e
4 changed files with 114 additions and 21 deletions
|
@ -68,6 +68,8 @@ When accessing a directory the file `index.gemini` or `index.gmi` is served.
|
|||
|
||||
Serve static files from specified root directory.
|
||||
|
||||
Directory listing may be enabled by adding `listdirectory: true`.
|
||||
|
||||
#### Proxy
|
||||
|
||||
Forward request to Gemini server at specified URL.
|
||||
|
@ -96,6 +98,7 @@ hosts:
|
|||
-
|
||||
path: /sites
|
||||
root: /home/gemini.rocks/data/sites
|
||||
listdirectory: true
|
||||
-
|
||||
path: ^/(help|info)$
|
||||
root: /home/gemini.rocks/data/help
|
||||
|
|
|
@ -22,6 +22,9 @@ type pathConfig struct {
|
|||
Proxy string
|
||||
Command string
|
||||
|
||||
// List directory entries
|
||||
ListDirectory bool
|
||||
|
||||
r *regexp.Regexp
|
||||
cmd []string
|
||||
}
|
||||
|
|
112
server.go
112
server.go
|
@ -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) {
|
||||
serveProxy(c, requestData, serve.Proxy)
|
||||
return
|
||||
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) {
|
||||
serveProxy(c, requestData, serve.Proxy)
|
||||
return
|
||||
}
|
||||
} else if serve.cmd != nil {
|
||||
if serve.r != nil && serve.r.Match(pathBytes) {
|
||||
serveCommand(c, serve.cmd)
|
||||
return
|
||||
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) {
|
||||
serveCommand(c, serve.cmd)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if serve.r != nil && serve.r.Match(pathBytes) {
|
||||
serveFile(c, requestData, path.Join(serve.Root, strippedPath))
|
||||
if serve.Proxy != "" {
|
||||
serveProxy(c, requestData, serve.Proxy)
|
||||
return
|
||||
} else if serve.cmd != nil {
|
||||
serveCommand(c, serve.cmd)
|
||||
return
|
||||
}
|
||||
serveFile(c, request, requestData, path.Join(serve.Root, strippedPath), serve.ListDirectory)
|
||||
return
|
||||
} else if serve.r == nil && strings.HasPrefix(request.Path, serve.Path) {
|
||||
serveFile(c, requestData, path.Join(serve.Root, strippedPath[len(serve.Path)-1:]))
|
||||
if serve.Proxy != "" {
|
||||
serveProxy(c, requestData, serve.Proxy)
|
||||
return
|
||||
} else if serve.cmd != nil {
|
||||
serveCommand(c, serve.cmd)
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
17
util.go
Normal file
17
util.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func formatFileSize(b int64) string {
|
||||
const unit = 1000
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.0f %cB",
|
||||
float64(b)/float64(div), "KMGTPE"[exp])
|
||||
}
|
Loading…
Reference in a new issue