190
package main
import (
"embed"
"fmt"
"io/fs"
"time"
_ "embed"
"html/template"
"io"
"net/http"
"os"
"path/filepath"
"strings"
)
//go:embed html/*
var embedHtml embed.FS
type TimeStr struct {
Full string
Short string
}
type DirEntry struct {
Icon string
Name string
Size string
Url string
mod int64
Mod TimeStr
}
func DateToInt(t time.Time) int { // bit size: y 12, mon 4, day 5
return t.Year()<<(12+4+5) | int(t.Month())<<(4+5) | t.Day()
}
func FmtTime(t time.Time, today int) TimeStr {
var pt TimeStr
pt.Full = t.Format("Mon 02 Jan 2006 15:04")
if DateToInt(t) == today {
pt.Short = "today " + t.Format("15:04")
} else {
pt.Short = t.Format("02 Jan 2006")
}
return pt
}
func Si(d int64) string {
f := float64(d)
i := 0
for f > 1024 {
f /= 1024
i++
}
s := fmt.Sprintf("%.2f", f)
s = strings.TrimRight(s, ".0")
return fmt.Sprintf("%s %cB", s, " KMGTPEZY"[i])
}
type TemplateData struct {
Path string
Dirs []DirEntry
Items []DirEntry
}
func (t *TemplateData) add(a DirEntry, size int64, dir bool) {
if dir {
a.Size = func() string {
n, e := os.ReadDir(filepath.Join(Config.HTTPDir, a.Url))
if e == nil {
return fmt.Sprintf("%d items", len(n))
}
return "0 items"
}()
a.Icon = "F"
t.Dirs = append(t.Dirs, a)
} else {
a.Icon = "f"
a.Size = Si(size)
t.Items = append(t.Items, a)
}
}
func (t *TemplateData) sortNewest() {
for _, tt := range []*[]DirEntry{&t.Items, &t.Dirs} {
for i := 0; i < len(*tt); i++ {
for j := i + 1; j < len(*tt); j++ {
if (*tt)[i].mod < (*tt)[j].mod {
(*tt)[i], (*tt)[j] = (*tt)[j], (*tt)[i]
}
}
}
}
}
var Temp *template.Template
var HttpCodes = map[int]string{
404: "Not Found",
500: "Internal Server Error",
403: "Forbidden",
401: "Unauthorized",
400: "Bad Request",
200: "OK",
}
func FillError(w io.Writer, err error, code int) bool {
if err == nil {
return false
}
_ = Temp.ExecuteTemplate(w, "index", map[string]string{
"Error": fmt.Sprintf("%d: %s", code, HttpCodes[code]),
})
return true
}
func customFileServer(root http.Dir) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
upath := r.URL.Path
f, err := root.Open(upath)
if FillError(w, err, 404) {
return
}
defer f.Close()
var fi fs.FileInfo
fi, err = f.Stat()
if FillError(w, err, 500) {
return
}
if fi.IsDir() {
var fi []fs.FileInfo
fi, err = f.Readdir(0)
if FillError(w, err, 500) {
return
}
t := TemplateData{Path: upath, Dirs: []DirEntry{}, Items: []DirEntry{}}
today := DateToInt(time.Now())
for _, d := range fi {
t.add(DirEntry{
Name: d.Name(),
Url: filepath.Join(upath, d.Name()),
mod: d.ModTime().Unix(),
Mod: FmtTime(d.ModTime(), today),
}, d.Size(), d.IsDir())
}
t.sortNewest()
Temp.ExecuteTemplate(w, "index", t)
return
}
http.FileServer(root).ServeHTTP(w, r)
})
}
func main() {
// Fs := os.DirFS(Config.HTTPDir)
http.Handle("/", customFileServer(http.Dir(Config.HTTPDir)))
// http.Handle("/", http.StripPrefix("/", FileServer(http.Dir(Config.HTTPDir))))
http.ListenAndServe(Config.Listen, nil)
}
var Config struct {
HTTPDir string
Listen string
}
func init() {
Temp = template.Must(template.ParseFS(embedHtml, "html/*"))
httpdir := os.Getenv("HTTP_DIR")
if httpdir == "" {
httpdir = "."
}
Config.HTTPDir, _ = filepath.Abs(httpdir)
Config.Listen = os.Getenv("ADDR")
if Config.Listen == "" {
Config.Listen = "127.0.0.1"
}
Config.Listen += ":"
n := len(Config.Listen)
Config.Listen += os.Getenv("PORT")
if len(Config.Listen) == n {
Config.Listen += "8844"
}
}