Implemented symlinks and mimetype for serve

This commit is contained in:
Nixietab 2026-10-15 21:41:16 -03:00
parent 523e519a3b
commit 605dc5352c
5 changed files with 81 additions and 16 deletions

View file

@ -5,6 +5,7 @@ import (
_ "embed"
"fmt"
"html/template"
"io"
"mime"
"net/http"
"net/url"
@ -30,6 +31,7 @@ type server struct {
rootReal string // symlink-resolved root, used for containment checks
hidden bool
footer string
follow bool // serve symlinks even when they point outside root
tpl *template.Template
}
@ -43,6 +45,7 @@ func newServer(cfg *Config) *server {
rootReal: rootReal,
hidden: cfg.Hidden,
footer: cfg.Footer,
follow: cfg.FollowSymlinks,
tpl: template.Must(template.New("index").Parse(indexHTML)),
}
}
@ -111,7 +114,7 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
return
}
if realPath != s.rootReal && !strings.HasPrefix(realPath, s.rootReal+string(os.PathSeparator)) {
if !s.follow && realPath != s.rootReal && !strings.HasPrefix(realPath, s.rootReal+string(os.PathSeparator)) {
http.NotFound(w, r) // symlink escaping the served root
return
}
@ -147,11 +150,48 @@ func (s *server) serveFile(w http.ResponseWriter, r *http.Request, path, name st
return
}
defer f.Close()
disp := "attachment"
if inlineFile(f, name) {
disp = "inline"
}
w.Header().Set("Content-Disposition",
mime.FormatMediaType("attachment", map[string]string{"filename": name}))
mime.FormatMediaType(disp, map[string]string{"filename": name}))
http.ServeContent(w, r, name, info.ModTime(), f)
}
func inlineFile(f *os.File, name string) bool {
if ct := mime.TypeByExtension(strings.ToLower(filepath.Ext(name))); ct != "" {
return inlineMIME(ct)
}
buf := make([]byte, 512)
n, err := f.Read(buf)
if _, serr := f.Seek(0, io.SeekStart); serr != nil {
return false
}
if err != nil && err != io.EOF {
return false
}
return inlineMIME(http.DetectContentType(buf[:n]))
}
func inlineMIME(ct string) bool {
if i := strings.IndexByte(ct, ';'); i >= 0 {
ct = strings.TrimSpace(ct[:i])
}
switch {
case strings.HasPrefix(ct, "image/"),
strings.HasPrefix(ct, "audio/"),
strings.HasPrefix(ct, "video/"),
strings.HasPrefix(ct, "text/"):
return ct != "text/html"
case ct == "application/pdf",
ct == "application/xml",
ct == "application/json":
return true
}
return false
}
func (s *server) serveListing(w http.ResponseWriter, r *http.Request, dirPath, urlPath string) {
entries, err := os.ReadDir(dirPath)
if err != nil {
@ -176,20 +216,31 @@ func (s *server) serveListing(w http.ResponseWriter, r *http.Request, dirPath, u
continue
}
isDir := e.IsDir()
var size int64
var mod time.Time
if info, err := e.Info(); err == nil { // lstat: does not follow symlinks
mod = info.ModTime()
if !isDir {
size = info.Size()
}
}
if e.Type()&os.ModeSymlink != 0 {
if ti, err := os.Stat(filepath.Join(dirPath, name)); err == nil {
isDir = ti.IsDir()
mod = ti.ModTime()
if isDir {
size = 0
} else {
size = ti.Size()
}
}
}
typ := "-"
if isDir {
typ = "dir"
} else if ext := strings.TrimPrefix(filepath.Ext(name), "."); ext != "" {
typ = strings.ToLower(ext)
}
var size int64
var mod time.Time
if info, err := e.Info(); err == nil {
mod = info.ModTime()
if !isDir {
size = info.Size()
}
}
rows = append(rows, row{
Name: name,
URL: dirURL + url.PathEscape(name),