made like cool file servery thingy
sophuwu sophie@skisiel.com
Sun, 23 Mar 2025 23:08:02 +0100
10 files changed,
299 insertions(+),
726 deletions(-)
M
dbfs/dbfs.go
→
dbfs/dbfs.go
@@ -3,12 +3,18 @@
import ( "fmt" bolt "go.etcd.io/bbolt" + "path/filepath" - "sophuwu.site/cdn/fileserver" "strings" ) -type DirEntry = fileserver.DirEntry +// DirEntry represents a file or directory entry +type DirEntry struct { + Name string // Name of the file or directory + FullName string // Full path of the file or directory + Size int // Size of the file in bytes + IsDir bool // Is true if the entry is a directory +} // DBFS represents a file system in a database type DBFS struct {
D
dir/dirent.go
@@ -1,62 +0,0 @@
-package dir - -import ( - "errors" - "io" - "io/fs" - "net/http" - "path/filepath" - "sophuwu.site/cdn/fileserver" -) - -func Open(path string) func(string) ([]byte, []fileserver.DirEntry, error) { - d := Dir{http.Dir(path)} - return d.GetEntry -} - -type Dir struct { - H http.FileSystem -} - -type DirEntry = fileserver.DirEntry - -func (d *Dir) GetEntry(path string) (data []byte, items []DirEntry, err error) { - var f http.File - f, err = d.H.Open(path) - if err != nil { - return - } - var fi fs.FileInfo - fi, err = f.Stat() - if err != nil { - return - } - if fi.IsDir() { - var de []fs.FileInfo - de, err = f.Readdir(0) - if err != nil { - return - } - items = []DirEntry{} - for _, d := range de { - items = append(items, DirEntry{ - Name: d.Name() + func() string { - if d.IsDir() { - return "/" - } - return "" - }(), - FullName: filepath.Join(path, d.Name()), - Size: int(d.Size()), - IsDir: d.IsDir(), - }) - } - return - } - if fi.Mode().IsRegular() || fi.Mode().Type() == fs.ModeSymlink { - data, err = io.ReadAll(f) - return - } - err = errors.New("not a regular file") - return -}
D
fileserver/fileserver.go
@@ -1,329 +0,0 @@
-package fileserver - -import ( - "crypto/sha256" - "crypto/subtle" - "encoding/base64" - "fmt" - "github.com/pquerna/otp/totp" - "html/template" - "io" - "net/http" - "os" - "path/filepath" - "sophuwu.site/cdn/config" - "strings" - "time" -) - -type DirEntry struct { - Name string - FullName string - Size int - IsDir bool -} - -func (d DirEntry) Si() string { - if d.IsDir { - return "" - } - f := float64(d.Size) - 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 -} - -var Temp *template.Template - -func FillTemp(w io.Writer, path string, items []DirEntry) error { - var data = TemplateData{Path: path, Dirs: []DirEntry{}, Items: []DirEntry{}} - for _, item := range items { - item.FullName = filepath.Join(path, item.Name) - if item.IsDir { - data.Dirs = append(data.Dirs, item) - } else { - data.Items = append(data.Items, item) - } - } - return Temp.ExecuteTemplate(w, "index", data) -} - -func FillUpload(w io.Writer, path string) error { - return Temp.ExecuteTemplate(w, "index", map[string]string{ - "Upload": path, - }) -} - -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 init() { - Temp = template.New("index") - Temp.Parse(`{{ define "index" }} -<!DOCTYPE html> -<html> -<head> -{{ if .Path }} -<title>{{ .Path }}</title> -{{ else }} -{{ if .Upload }} -<title>Upload</title> -{{ else }} -<title>Error</title> -{{ end }} -{{ end }} -<style> -:root {--bord: #ccc;--fg: #fff;} -body {width: calc(100% - 2ch);margin: auto auto auto auto ;max-width: 800px;background: #262833;color: var(--fg);font-family: sans-serif;} -.trees {width: 100%;display: flex;flex-direction: column;padding: 0;margin: auto auto;border: 1px solid var(--bord);border-radius: 1ch;overflow: hidden;} -.trees a {display: contents;text-decoration: none;} -.filelabel {padding: 8px;font-size: 1rem;width: auto;margin: 0;display: grid;grid-template-columns: auto auto;grid-gap: 1ch;justify-content: space-between;align-items: center;border-radius: 0;background: transparent;} -.trees > a > * {border-bottom: 1px solid var(--bord);background: #1c1e26;} -.trees > a > *:hover {background: #2c2e46;} -.trees > a:last-child > * {border-bottom: none;} -a {color: var(--fg);text-decoration: none;} -.filelabel > :last-child {text-align: right;} -</style> -</head> -<body> -{{ if .Path }} -<h1>Index of: {{ .Path }}</h1> -<div class="trees"> -{{ range .Dirs }} -<a href="{{ .FullName }}"><div class="filelabel"><span>{{ .Name }}</span><span>{{ .Si }}</span></div></a> -{{ end }} -{{ range .Items }} -<a href="{{ .FullName }}"><div class="filelabel"><span>{{ .Name }}</span><span>{{ .Si }}</span></div></a> -{{ end }} -</div> -{{ else }} -{{ if .Error }} -<h1>{{ .Error }}</h1> -{{ else }} -{{ if .Upload }} -<h1>Upload</h1> -<form class="trees" enctype="multipart/form-data" action="{{ .Upload }}" method="post"> - <div class="filelabel"><span>Path:</span><input type="text" name="path" /></div> - <div class="filelabel"><span>File:</span><input type="file" name="myFile" /></div> - <div class="filelabel"><span>Username:</span><input type="text" name="username" /></div> - <div class="filelabel"><span>Password:</span><input type="password" name="password" /></div> - <div class="filelabel"><span>OTP:</span><input type="text" name="otp" /></div> - <div class="filelabel"><span></span><input type="submit" value="Upload" /></div> -</form> -{{ end }} -{{ end }} -{{ end }} -</body> -</html> -{{ end }}`) -} - -type readseek struct { - bb []byte - i int -} - -func (r *readseek) Read(p []byte) (n int, err error) { - if len(p) > len(r.bb)-r.i { - n = len(r.bb) - r.i - } else { - n = len(p) - } - for i := 0; i < n; i++ { - p[i] = r.bb[r.i+i] - } - r.i += n - if r.i >= len(r.bb) { - err = io.EOF - } - return -} -func (r *readseek) Seek(offset int64, whence int) (int64, error) { - var n int - switch whence { - case io.SeekStart: - n = int(offset) - case io.SeekCurrent: - n = r.i + int(offset) - case io.SeekEnd: - n = len(r.bb) + int(offset) - } - if n < 0 || n > len(r.bb) { - return 0, fmt.Errorf("invalid offset") - } - r.i = n - return int64(r.i), nil -} - -func (r *readseek) nullify() { - r.bb = []byte{} -} -func newReadSeek(b []byte) *readseek { - return &readseek{b, 0} -} - -type writer struct { - bb []byte -} - -func (r *writer) Write(p []byte) (n int, err error) { - r.bb = append(r.bb, p...) - return len(p), nil -} -func (r *writer) nullify() { - r.bb = []byte{} -} -func (r *writer) convert() *readseek { - return newReadSeek(r.bb) -} -func newWriter() *writer { - return &writer{bb: []byte{}} -} - -func Handler(prefix string, dir func(string) ([]byte, []DirEntry, error)) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ww := newWriter() - data, items, err := dir(strings.TrimPrefix(r.URL.Path, prefix)) - if !FillError(ww, err, 404) && data != nil { - ww.Write(data) - } else { - err = FillTemp(ww, r.URL.Path, append([]DirEntry{{ - Name: "../", - FullName: filepath.Dir(strings.TrimSuffix(r.URL.Path, "/")), - Size: 0, - IsDir: true, - }}, items...)) - FillError(ww, err, 500) - } - rs := ww.convert() - ww.nullify() - w.WriteHeader(http.StatusNotModified) - http.ServeContent(w, r, filepath.Base(r.URL.Path), time.Time{}, rs) - rs.nullify() - }) -} - -func Handle(prefix string, dir func(string) ([]byte, []DirEntry, error)) { - http.Handle(prefix, Handler(prefix, dir)) -} - -func VerifyOtp(p, o string) bool { - b, err := os.ReadFile(config.OtpPath) - if err != nil { - return false - } - s := string(b) - s = strings.Split(s, "\n")[0] - b, err = base64.StdEncoding.DecodeString(strings.TrimSpace(s)) - if err != nil { - return false - } - b = b[:32] - bb := sha256.Sum256([]byte(p)) - for i := 0; i < len(b); i++ { - b[i] = b[i] ^ bb[i] - } - s = string(b) - return totp.Validate(o, s) -} -func VerifyBasicAuth(u, p string) bool { - b, err := os.ReadFile(config.OtpPath) - if err != nil { - return false - } - ss := strings.Split(string(b), "\n") - if len(ss) < 2 { - return false - } - b, err = base64.StdEncoding.DecodeString(strings.TrimSpace(ss[1])) - if err != nil { - return false - } - bb := sha256.Sum256([]byte(u + ";" + p)) - return subtle.ConstantTimeCompare(b, bb[:]) == 1 -} - -func Authenticate(next http.HandlerFunc) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - user, apass, authOK := r.BasicAuth() - if !authOK || !VerifyBasicAuth(user, apass) { - w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) - http.Error(w, "Unauthorized.", http.StatusUnauthorized) - return - } - next.ServeHTTP(w, r) - }) -} - -func UpHandler(prefix string, save func(string, []byte) error) http.Handler { - return Authenticate(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - if r.Method == "GET" { - err := FillUpload(w, prefix) - if FillError(w, err, 500) { - return - } - return - } - if r.Method == "POST" { - r.ParseMultipartForm(256 * 1024 * 1024) - path := r.Form.Get("path") - - username := r.Form.Get("username") - password := r.Form.Get("password") - otp := r.Form.Get("otp") - if !VerifyOtp(username+";"+password, otp) { - FillError(w, fmt.Errorf("unauthorized"), 401) - return - } - - file, _, err := r.FormFile("myFile") - if FillError(w, err, 400) { - return - } - defer file.Close() - data, err := io.ReadAll(file) - if FillError(w, err, 500) { - return - } - err = save(path, data) - if FillError(w, err, 500) { - return - } - http.Redirect(w, r, strings.ToLower(prefix)+path, http.StatusFound) - return - } - FillError(w, fmt.Errorf("method not allowed"), 405) - })) -} - -func UpHandle(prefix string, save func(string, []byte) error) { - http.Handle(prefix, UpHandler(prefix, save)) -}
M
go.mod
→
go.mod
@@ -7,6 +7,7 @@
require ( github.com/blang/vfs v1.0.0 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/pquerna/otp v1.4.0 // indirect golang.org/x/sys v0.4.0 // indirect )
M
go.sum
→
go.sum
@@ -5,6 +5,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
A
html/filelabel.html
@@ -0,0 +1,10 @@
+{{ define "filelabel" }} +<a href="{{ .Url }}"> + <div class="filelabel"> + <span class="icon">{{ .Icon }}</span> + <span>{{ .Name }}</span> + <span class="right">{{ .Size }}</span> + <span class="right" title="{{ .Mod.Full }}">{{ .Mod.Short }}</span> + </div> +</a> +{{ end }}
A
html/index.html
@@ -0,0 +1,106 @@
+{{ define "index" }} +<!DOCTYPE html> +<html> +<head> +{{ if .Path }} +<title>{{ .Path }}</title> +{{ else }} +{{ if .Upload }} +<title>Upload</title> +{{ else }} +<title>Error</title> +{{ end }} +{{ end }} +<meta name="viewport" content="width=device-width, initial-scale=1"> +<style> +:root{ + color:white; + background: #262833; + @media (max-width: 2000px) and (min-width: 1500px) { + font-size: 100%; + } + @media (max-width: 1500px) and (min-width: 1200px) { + font-size: 85%; + } + @media (max-width: 1200px) and (min-width: 800px) { + font-size: 75%; + } + + @media (max-width: 800px) and (min-width: 600px) { + font-size: 70%; + } + @media (max-width: 600px) and (min-width: 400px) { + font-size: 65%; + } + @media (max-width: 400px) { + font-size: 60%; + } + +} +body { + width: 90ch; + max-width: calc(100% - 2ch); + margin: 0 auto 0 auto; + font-family: sans-serif; +} + +.trees > a { + text-decoration: none; + display:contents; + color: unset; +} + +.filelabel { + padding: 1em; + margin: 0; + display: grid; + grid-template-columns: 2ch calc(60% - 4em - 2ch) max(15ch,20%) max(15ch,20%); + justify-content: space-between; + align-items: center; + border-radius: 1em; + margin-bottom: .5lh; +} +.filelabel:hover {background: #2c2e46;} +.right{text-align: right;} + +@font-face{ + font-family: "sophuwuicons"; + src: url("data:font/ttf;base64,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") format("truetype"); +} +.icon { + font-family: "sophuwuicons"; + font-size: 2rem; +} +</style> +</head> +<body> +{{ if .Path }} +<h1>Index of: {{ .Path }}</h1> +<div class="trees"> +{{ range .Dirs }} + {{ template "filelabel" . }} +{{ end }} +{{ range .Items }} + {{ template "filelabel" . }} +{{ end }} +</div> +{{ else }} +{{ if .Error }} +<h1>{{ .Error }}</h1> +{{ else }} +{{ if .Upload }} +<h1>Upload</h1> +<form class="trees" enctype="multipart/form-data" action="{{ .Upload }}" method="post"> + <div class="filelabel"><span>Path:</span><input type="text" name="path" /></div> + <div class="filelabel"><span>File:</span><input type="file" name="myFile" /></div> + <div class="filelabel"><span>Username:</span><input type="text" name="username" /></div> + <div class="filelabel"><span>Password:</span><input type="password" name="password" /></div> + <div class="filelabel"><span>OTP:</span><input type="text" name="otp" /></div> + <div class="filelabel"><span></span><input type="submit" value="Upload" /></div> +</form> +{{ end }} +{{ end }} +{{ end }} +</body> +</html> +{{ end }}
M
main.go
→
main.go
@@ -1,59 +1,190 @@
package main import ( - "context" - "errors" + "embed" "fmt" + "io/fs" + "time" + + _ "embed" + "html/template" + "io" "net/http" "os" - "os/signal" - "sophuwu.site/cdn/config" - "sophuwu.site/cdn/dbfs" - "sophuwu.site/cdn/dir" - "sophuwu.site/cdn/fileserver" + "path/filepath" + "strings" ) -func main() { - config.Get() - fmt.Println(config.DbPath, config.OtpPath, config.Port, config.Addr) +//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", +} - var err error - var db *dbfs.DBFS +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 +} - if config.DbPath != "" { - db, err = dbfs.OpenDB(config.DbPath) - if err != nil { - fmt.Println(err) +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 } - fileserver.Handle("/x/", db.GetEntry) - if config.OtpPath != "" { - fileserver.UpHandle("/X/", db.PutFile) + 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 } - } - fileserver.Handle("/", dir.Open(config.HttpDir)) + http.FileServer(root).ServeHTTP(w, r) + }) +} - server := http.Server{ - Addr: config.Addr + ":" + config.Port, - Handler: nil, +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) - go func() { - err = server.ListenAndServe() - if err != nil && !errors.Is(err, http.ErrServerClosed) { - fmt.Println(err) - } - }() - quit := make(chan os.Signal, 1) - signal.Notify(quit, os.Interrupt) - <-quit - fmt.Println("Closing databases") - err = db.Close() - if err != nil { - fmt.Println(err) + Config.Listen = os.Getenv("ADDR") + if Config.Listen == "" { + Config.Listen = "127.0.0.1" } - fmt.Println("Closed databases") - fmt.Println("Stopping server") - _ = server.Shutdown(context.Background()) - fmt.Println("Server stopped") + Config.Listen += ":" + n := len(Config.Listen) + Config.Listen += os.Getenv("PORT") + if len(Config.Listen) == n { + Config.Listen += "8844" + } + }
D
test/index.html
@@ -1,71 +0,0 @@
-{{ define "index" }} -<!DOCTYPE html> -<html> -<head> -{{ if .Path }} -<title>{{ .Path }}</title> -{{ else }} -{{ if .Upload }} -<title>Upload</title> -{{ else }} -<title>Error</title> -{{ end }} -{{ end }} -<style> -:root {--bord: #ccc;--fg: #fff;} -body {width: calc(100% - 2ch);margin: auto auto auto auto ;max-width: 800px;background: #262833;color: var(--fg);font-family: sans-serif;} -.trees {width: 100%;display: flex;flex-direction: column;padding: 0;margin: auto auto;border: 1px solid var(--bord);border-radius: 1ch;overflow: hidden;} -.trees a {display: contents;text-decoration: none;} -.filelabel {padding: 8px;font-size: 1rem;width: auto;margin: 0;display: grid;grid-template-columns: auto auto;grid-gap: 1ch;justify-content: space-between;align-items: center;border-radius: 0;background: transparent;} -.trees > a > * {border-bottom: 1px solid var(--bord);background: #1c1e26;} -.trees > a > *:hover {background: #2c2e46;} -.trees > a:last-child > * {border-bottom: none;} -a {color: var(--fg);text-decoration: none;} -.filelabel > :last-child {text-align: right;} -@font-face { - font-family: "sophuwuicons"; - /*src: url("/static/icons.woff2") format("woff2"),*/ - src: url("http://soph/font/sophicons/sophuwuicons.woff2") format("woff2"), - /*url("/static/icons.ttf") format("truetype")*/ - url("http://soph/font/sophicons/sophuwuicons.ttf") format("truetype") -} -.icon { - font-family: "sophuwuicons"; - font-size: inherit; - font-weight: normal !important; - font-style: normal; - height: 1em; -} -</style> -</head> -<body> -{{ if .Path }} -<h1>Index of: {{ .Path }}</h1> -<div class="trees"> -{{ range .Dirs }} -<a href="{{ .Name }}"><div class="filelabel"><span class="icon"></span><span>{{ .Name }}</span><span></span></div></a> -{{ end }} -{{ range .Items }} -<a href="{{ .Name }}"><div class="filelabel"><span class="icon"></span><span>{{ .Name }}</span><span>{{ .Size }}</span></div></a> -{{ end }} -</div> -{{ else }} -{{ if .Error }} -<h1>{{ .Error }}</h1> -{{ else }} -{{ if .Upload }} -<h1>Upload</h1> -<form class="trees" enctype="multipart/form-data" action="{{ .Upload }}" method="post"> - <div class="filelabel"><span>Path:</span><input type="text" name="path" /></div> - <div class="filelabel"><span>File:</span><input type="file" name="myFile" /></div> - <div class="filelabel"><span>Username:</span><input type="text" name="username" /></div> - <div class="filelabel"><span>Password:</span><input type="password" name="password" /></div> - <div class="filelabel"><span>OTP:</span><input type="text" name="otp" /></div> - <div class="filelabel"><span></span><input type="submit" value="Upload" /></div> -</form> -{{ end }} -{{ end }} -{{ end }} -</body> -</html> -{{ end }}
D
test/test.go
@@ -1,221 +0,0 @@
-package main - -import ( - "fmt" - "io/fs" - - "html/template" - "io" - "net/http" - "os" - "path/filepath" - "strings" -) - -type DirEntry struct { - Name string - Size string - Url string - mod int64 -} - -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 { - - t.Dirs = append(t.Dirs, a) - } else { - 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{}} - for _, d := range fi { - t.add(DirEntry{ - Name: d.Name(), - Url: filepath.Join(upath, d.Name()), - mod: d.ModTime().Unix(), - }, 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.New("index").Parse(embedHtml) - - 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" - } - -} - -var embedHtml = `{{ define "index" }} -<!DOCTYPE html> -<html> -<head> -{{ if .Path }} -<title>{{ .Path }}</title> -{{ else }} -{{ if .Upload }} -<title>Upload</title> -{{ else }} -<title>Error</title> -{{ end }} -{{ end }} -<style> -:root {--bord: #ccc;--fg: #fff;} -body {width: calc(100% - 2ch);margin: auto auto auto auto ;max-width: 800px;background: #262833;color: var(--fg);font-family: sans-serif;} -.trees {width: 100%;display: flex;flex-direction: column;padding: 0;margin: auto auto;border: 1px solid var(--bord);border-radius: 1ch;overflow: hidden;} -.trees a {display: contents;text-decoration: none;} -.filelabel {padding: 8px;font-size: 1rem;width: auto;margin: 0;display: grid;grid-template-columns: auto auto;grid-gap: 1ch;justify-content: space-between;align-items: center;border-radius: 0;background: transparent;} -.trees > a > * {border-bottom: 1px solid var(--bord);background: #1c1e26;} -.trees > a > *:hover {background: #2c2e46;} -.trees > a:last-child > * {border-bottom: none;} -a {color: var(--fg);text-decoration: none;} -.filelabel > :last-child {text-align: right;} -@font-face { - font-family: "sophuwuicons"; - src: url("data:font/ttf;base64,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AH4AgACBAOwA7gC6B3VuaTAwMDAHdW5pMDAwMQd1bmkwMDAyB3VuaTAwMDMHdW5pMDAwNAd1bmkwMDA1B3VuaTAwMDYHdW5pMDAwNwd1bmkwMDA4B3VuaTAwMDkHdW5pMDAwQQd1bmkwMDBCB3VuaTAwMEMHdW5pMDAwRAd1bmkwMDBFB3VuaTAwMEYHdW5pMDAxMAd1bmkwMDExB3VuaTAwMTIHdW5pMDAxMwd1bmkwMDE0B3VuaTAwMTUHdW5pMDAxNgd1bmkwMDE3B3VuaTAwMTgHdW5pMDAxOQd1bmkwMDFBB3VuaTAwMUIHdW5pMDAxQwd1bmkwMDFEB3VuaTAwMUUHdW5pMDAxRgd1bmkwMDdGB3VuaTAwODAHdW5pMDA4MQd1bmkwMDgyB3VuaTAwODMHdW5pMDA4NAd1bmkwMDg1B3VuaTAwODYHdW5pMDA4Nwd1bmkwMDg4B3VuaTAwODkHdW5pMDA4QQd1bmkwMDhCB3VuaTAwOEMHdW5pMDA4RAd1bmkwMDhFB3VuaTAwOEYHdW5pMDA5MAd1bmkwMDkxB3VuaTAwOTIHdW5pMDA5Mwd1bmkwMDk0B3VuaTAwOTUHdW5pMDA5Ngd1bmkwMDk3B3VuaTAwOTgHdW5pMDA5OQd1bmkwMDlBB3VuaTAwOUIHdW5pMDA5Qwd1bmkwMDlEB3VuaTAwOUUHdW5pMDA5Rgd1bmkwMEEwB3VuaTAwQUQHdW5pMDBCMgd1bmkwMEIzB3VuaTAwQjkAAAAB//8AAgAAAAEAAAAA4p8rRgAAAADjXf2mAAAAAOQFcDk=") format("truetype"); -} -.icon { - font-family: "sophuwuicons"; - font-size: inherit; - font-weight: normal !important; - font-style: normal; -} -</style> -</head> -<body> -{{ if .Path }} -<h1>Index of: {{ .Path }}</h1> -<div class="trees"> -<a href=".."><div class="filelabel"><span><b class="icon">F</b> ..</span><span></span></div></a> -{{ range .Dirs }} -<a href="{{ .Url }}"><div class="filelabel"><span><b class="icon">f</b> {{ .Name }}</span><span></span></div></a> -{{ end }} -{{ range .Items }} -<a href="{{ .Url }}"><div class="filelabel"><span><b class="icon">f</b> {{ .Name }}</span><span>{{ .Size }}</span></div></a> -{{ end }} -</div> -{{ else }} -{{ if .Error }} -<h1>{{ .Error }}</h1> -{{ else }} -{{ if .Upload }} -<h1>Upload</h1> -<form class="trees" enctype="multipart/form-data" action="{{ .Upload }}" method="post"> - <div class="filelabel"><span>Path:</span><input type="text" name="path" /></div> - <div class="filelabel"><span>File:</span><input type="file" name="myFile" /></div> - <div class="filelabel"><span>Username:</span><input type="text" name="username" /></div> - <div class="filelabel"><span>Password:</span><input type="password" name="password" /></div> - <div class="filelabel"><span>OTP:</span><input type="text" name="otp" /></div> - <div class="filelabel"><span></span><input type="submit" value="Upload" /></div> -</form> -{{ end }} -{{ end }} -{{ end }} -</body> -</html> -{{ end }}`