twins/server_file.go

176 lines
3.8 KiB
Go

package main
import (
"fmt"
"io"
"net"
"net/url"
"os"
"path"
"path/filepath"
"sort"
"strings"
"github.com/h2non/filetype"
)
func serveDirList(c net.Conn, request *url.URL, dirPath string) {
var (
files []os.FileInfo
numDirs int
numFiles int
)
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() || info.Mode()&os.ModeSymlink != 0 {
numDirs++
} else {
numFiles++
}
if info.IsDir() {
return filepath.SkipDir
}
return nil
})
if err != nil {
writeStatus(c, 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, statusSuccess, "text/gemini; charset=utf-8")
fmt.Fprintf(c, "# %s\r\n", request.Path)
if numDirs > 0 || numFiles > 0 {
if numDirs > 0 {
if numDirs == 1 {
c.Write([]byte("1 directory"))
} else {
fmt.Fprintf(c, "%d directories", numDirs)
}
}
if numFiles > 0 {
if numDirs > 0 {
c.Write([]byte(" and "))
}
if numDirs == 1 {
c.Write([]byte("1 file"))
} else {
fmt.Fprintf(c, "%d files", numFiles)
}
}
c.Write([]byte("\r\n\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, filePath string, listDir bool) {
fi, err := os.Stat(filePath)
if err != nil {
writeStatus(c, statusNotFound)
return
}
if mode := fi.Mode(); mode.IsDir() {
if len(request.Path) == 0 || request.Path[len(request.Path)-1] != '/' {
// Add trailing slash
writeHeader(c, statusRedirectPermanent, request.String()+"/")
return
}
_, err := os.Stat(path.Join(filePath, "index.gmi"))
if err != nil {
_, err := os.Stat(path.Join(filePath, "index.gemini"))
if err != nil {
if listDir {
serveDirList(c, request, filePath)
return
}
writeStatus(c, statusNotFound)
return
}
filePath = path.Join(filePath, "index.gemini")
} else {
filePath = path.Join(filePath, "index.gmi")
}
}
// Open file
file, _ := os.Open(filePath)
defer file.Close()
// Read file header
buf := make([]byte, 261)
n, _ := file.Read(buf)
// Write response header
size := int64(-1)
info, err := file.Stat()
if err == nil {
size = info.Size()
}
var mimeType string
if strings.HasSuffix(filePath, ".html") && strings.HasSuffix(filePath, ".htm") {
mimeType = "text/html; charset=utf-8"
} else if strings.HasSuffix(filePath, ".txt") && strings.HasSuffix(filePath, ".text") {
mimeType = "text/plain; charset=utf-8"
} else if !strings.HasSuffix(filePath, ".gmi") && !strings.HasSuffix(filePath, ".gemini") {
kind, _ := filetype.Match(buf[:n])
if kind != filetype.Unknown {
mimeType = kind.MIME.Value
}
}
if mimeType == "" {
mimeType = "text/gemini; charset=utf-8"
}
var meta string
if !config.DisableSize && size >= 0 {
meta = fmt.Sprintf("%s; size=%d", mimeType, size)
} else {
meta = mimeType
}
writeHeader(c, statusSuccess, meta)
// Write body
c.Write(buf[:n])
io.Copy(c, file)
}