git.sophuwu.com > cdn
started and finished writing the frontend in js, also added thumbnail gen for mp4 on backend
made the backend a bit faster and more secure too
sophuwu sophie@skisiel.com
Thu, 27 Mar 2025 18:00:45 +0100
commit

20aed60b5bddf0ac9bf320f294443dc01ad514a6

parent

8c7d527bb92fa7bf6025e13d77a480106261f35d

6 files changed, 404 insertions(+), 170 deletions(-)

jump to
A html/error.html

@@ -0,0 +1,13 @@

+{{ define "error" }} +<!DOCTYPE html> +<html lang="en"> +<head> + {{ template "head" . }} + <title>{{ .Code }}: {{ .Name }}</title> +</head> +<body> + <h1>{{ .Code }}: {{ .Name }}</h1> + <p>{{ .Message }}</p> +</body> +</html> +{{ end }}
M html/filelabel.htmlhtml/filelabel.html

@@ -1,10 +1,10 @@

{{ define "filelabel" }} -<a href="{{ .Url }}"> - <div class="filelabel"> +<a href="{{ .Url }}" id="{{ .Name }}" class="filelabel"> <span class="icon">{{ .Icon }}</span> <span class="file-name">{{ .Name }}</span> - <span class="right">{{ .Size }}</span> - <span class="right mod-time" title="{{ .Mod.Full }}">{{ .Mod.Short }}</span> - </div> + <span class="right" >{{ .Size }}</span> + <span class="right timestr" title="{{ .Mod.Full }}">{{ .Mod.Short }}</span> + <input type="hidden" class="mod-time" value="{{ .ModUnix }}" /> + <input type="hidden" class="size" value="{{ .SizeN }}" /> </a> {{ end }}
A html/head.html

@@ -0,0 +1,138 @@

+{{ define "head" }} +<meta charset="UTF-8"> +<style> +:root{ + color:white; + background: #262833; + @media (min-width: 2000px){ + font-size: 115%; + } + @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: 55%; + } + @media (max-width: 400px) { + font-size: 35%; + } + +} +.trees { + display:contents; +} +body { + width: 90ch; + max-width: calc(100% - 2ch); + margin: 0 auto 0 auto; + font-family: sans-serif; +} +header { + display: flex; + margin: 1em 0; + align-items: center; + height: 1.5lh; + padding: 0; +} +header .icon { + font-size: 130%; +} +h1 { + margin: 0 0 0 1ch; + padding: 0; + font-size: 1.5rem; + height: 1lh; +} +a { + text-decoration: none; + color: unset; +} +.button { + padding:0.5ch 1ch; + height: 1.5lh; + margin: 0; + display: grid; + width: min-content; + grid-template-columns: 2.5em 3em; + justify-content: space-between; + align-items: center; + border-radius: 1em; + background: #483B4A; +} +.filelabel, div.filelabel { + padding: 1ch 2ch; + margin: 0; + display: grid; + grid-template-columns: 2.5em calc(60% - 4em - 2ch) max(15ch,20%) max(20ch,20%); + justify-content: space-between; + align-items: center; + border-radius: 1em; + margin-bottom: .5em; + width: calc(100% - 4ch); +} +.button:hover, +a.filelabel:hover {background: #505258;} + +.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; +} +.CurSort { + background: #525359; +} +.CurSort.sorter::after { + content: "+"; + margin: 0; + font-family: sophuwuicons; + font-size: 1.5rem; + width: min-content; + padding: 0; +} +.rev>.CurSort.sorter::after { + content: '-'!important; +} +.icon > img { + max-width: 2rem; + max-height: 2rem; + width: auto; + height: auto; +} +.sorter { + cursor: pointer; + padding: 1em 1em; + margin: 0; + height: 1rlh; + font-size: 1em; + display: grid; + grid-template-columns: auto auto; + justify-content: space-between; + border-radius: 1ch; +} +.sorter:hover { + background:#505258; +} +div.filelabel{ + margin: 0 0; + padding: 0 0 0 1lh; + width: calc(100% - 1lh); + background: #131419; + overflow: clip; +} +</style> +{{ end }}
M html/index.htmlhtml/index.html

@@ -1,17 +1,9 @@

{{ define "index" }} <!DOCTYPE html> -<html> +<html lang="en"> <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"> + {{ template "head" . }} + <title>{{ .Path }}</title> <script> function fileExt(url) { let i = url.toString().toLowerCase().lastIndexOf(".");

@@ -20,106 +12,143 @@ return "";

} return url.toString().toLowerCase().substring(url.toString().lastIndexOf(".")); } - const imgExts = [".jpg", ".jpeg", ".png", ".gif", ".webp"]; + const imgExts = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".mp4"]; var dirPath = "{{ .Path }}"; if (!dirPath.endsWith("/")) { dirPath += "/"; } function IconCheck(elem) { let icon = elem.querySelector(".icon"); - if (icon.innerText === "F") { - return; - } let url = elem.querySelector(".file-name").innerText; let ext = fileExt(url); if (imgExts.includes(ext)) { - let mod = elem.querySelector(".mod-time").title.replaceAll(" ", "_").replaceAll(":", "_"); + let mod = elem.querySelector(".mod-time").value.replaceAll(" ", "_").replaceAll(":", "_"); url = dirPath + url; - icon.innerHTML = "<img src='"+url+".png?icon=" + url + "&mod="+ mod +"' alt='f'/>"; - } else if (ext === ".mp4") { - icon.innerText = "v"; + icon.innerHTML = "<img src='"+url+".png?icon=" + url + "&mod="+ mod +"' alt='a'/>"; + } + } + function ZeroPad(num, size) { + num = num.toString(); + for (let l = num.length;l < size;l++)num = "0" + num.toString(); + return num; + } + + function reverseList(list){ + let c = list.childElementCount; + for (i = 1; i < c;i++){ + list.insertBefore(list.children[i],list.firstChild); + } + Reversed = true; + } + function AlphabetSort(list) { + let i, switching, b, shouldSwitch; + switching = true; + while (switching) { + switching = false; + b = list.children; + for (i = 0; i < (b.length - 1); i++) { + shouldSwitch = false; + if (b[i].id.toLowerCase() > b[i + 1].id.toLowerCase()) { + shouldSwitch = true; + break; + } + } + if (shouldSwitch) { + b[i].parentNode.insertBefore(b[i + 1], b[i]); + switching = true; + } + } + Reversed = false; + currentSort = "AlphabetSort"; + } + function ModSizeSort(list,size=false){ + let classs = ".mod-time"; + if (size===true)classs=".size"; + let arr =new Array(...list.children); + arr.sort((b,a)=>(a.querySelector(classs).value)-(b.querySelector(classs).value)); + arr.forEach(e=>e.parentNode.appendChild(e)); + Reversed = false; + } + function ModSort(list){ + ModSizeSort(list,false); + currentSort = "ModSort"; + } + function SizeSort(list){ + ModSizeSort(list,true); + currentSort = "SizeSort"; + } + var currentSort = "ModSort"; + var Reversed = false; + function Id2Fn(id){ + let fn = { + AlphabetSort: AlphabetSort, + ModSort: ModSort, + SizeSort: SizeSort, + }[id]; + if (currentSort === id && !Reversed) { + return function (list){fn(list);reverseList(list);} + } + else { + return function (list){fn(list);}; } } + function SortWith(elem) { + let fn = Id2Fn(elem.id); + document.querySelectorAll(".dirs,.files").forEach(e =>fn(e)); + if (Reversed) elem.parentElement.classList.add("rev"); + else elem.parentElement.classList.remove("rev"); + document.querySelectorAll(".CurSort").forEach(e => e.classList.remove("CurSort")); + elem.classList.add("CurSort"); + localStorage.setItem("sort", currentSort); + localStorage.setItem("reversed", Reversed.toString()); + } + + document.addEventListener("DOMContentLoaded", function() { - document.querySelectorAll(".filelabel").forEach(filelement => { - IconCheck(filelement); + document.querySelectorAll(".files > .filelabel").forEach(filelement =>IconCheck(filelement)); + document.querySelectorAll(".timestr").forEach(elem => { + // let t= new Date(Date.parse("15 Mar 2025 15:23 +0000")) + let t =new Date(Date.parse(elem.title)); + if (elem.innerText[0] >= '0' && elem.innerText[0] <= '9') { + elem.innerText = t.getFullYear()+"-"+ZeroPad((t.getMonth()+1),2)+"-"+ZeroPad(t.getDate(),2); + } else { + elem.innerText = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][t.getDay()] + ", " + ZeroPad(t.getHours(),2) + ":" + ZeroPad(t.getMinutes(),2); + } }); + currentSort = localStorage.getItem("sort"); + if (currentSort === null) { + currentSort = "ModSort"; + } + Reversed = localStorage.getItem("reversed") !== "true"; + if (currentSort==="ModSort"&&Reversed)return; + SortWith(document.getElementById(currentSort)); }); </script> -<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; -} -.icon > img { - max-width: 2rem; - max-height: 2rem; - width: auto; - height: auto; -} -</style> </head> <body> {{ if .Path }} +<header> +{{ if eq .Path "/" }} +{{ else }} +<a href=".." class="button"> + <span class="icon"><</span><span>Back</span> +</a> +{{ end }} <h1>Index of: {{ .Path }}</h1> -<div class="trees"> +</header> +<div class="filelabel"> + <span class="icon">l</span> + <span id="AlphabetSort" onclick="SortWith(this)" class="sorter">Name</span> + <span id="SizeSort" onclick="SortWith(this)" class="right sorter">Size</span> + <span id="ModSort" onclick="SortWith(this)" class="right CurSort sorter">Time Modified</span> +</div> +<br> +<div class="trees dirs"> {{ range .Dirs }} {{ template "filelabel" . }} {{ end }} +</div> +<div class="trees files"> {{ range .Items }} {{ template "filelabel" . }} {{ end }}
M imgconv/imgconv.goimgconv/imgconv.go

@@ -5,6 +5,8 @@ "bytes"

"fmt" "github.com/nfnt/resize" "image/gif" + "os" + "os/exec" "strings" // "gocv.io/x/gocv"

@@ -37,24 +39,51 @@ }

return buf.Bytes(), nil } -func Media2Icon(path string, file io.Reader) ([]byte, error) { +func getFirstFrame(path string) (image.Image, error) { + + // ffmpeg -i input.mp4 -vframes 1 -f image2pipe -vcodec png - + // -vf "select='eq(pict_type\,I)'" -frames:v 1 + var out bytes.Buffer + cmd := exec.Command("ffmpeg", "-hide_banner", "-loglevel", "error", "-i", path, "-vf", "select='eq(pict_type\\,I)'", "-frames:v", "1", "-vsync", "0", "-f", "image2pipe", "-c:v", "png", "-") + // cmd.Stdin = file + cmd.Stdout = &out + cmd.Stderr = os.Stdout + err := cmd.Run() + if err != nil { + return nil, fmt.Errorf("error running ffmpeg: %v", err) + } + var img image.Image + img, err = png.Decode(&out) + if err != nil { + return nil, fmt.Errorf("error decoding image: %v", err) + } + return img, nil +} + +var medidaFuncs = map[string]func(file io.Reader) (image.Image, error){ + ".jpg": jpeg.Decode, + ".jpeg": jpeg.Decode, + ".png": png.Decode, + ".webp": webp.Decode, + ".gif": gif.Decode, +} + +func Media2Icon(path string) ([]byte, error) { var img image.Image var err error - switch strings.ToLower(filepath.Ext(path)) { - case ".jpg", ".jpeg": - img, err = jpeg.Decode(file) - break - case ".png": - img, err = png.Decode(file) - break - case ".webp": - img, err = webp.Decode(file) - break - case ".gif": - img, err = gif.Decode(file) - break - default: - err = fmt.Errorf("unsupported image format: %s", filepath.Ext(path)) + ext := strings.ToLower(filepath.Ext(path)) + if ext == ".mp4" { + img, err = getFirstFrame(path) + } else if fn, ok := medidaFuncs[ext]; ok { + var file *os.File + file, err = os.Open(path) + if err != nil { + return nil, fmt.Errorf("error opening file: %v", err) + } + img, err = fn(file) + file.Close() + } else { + err = fmt.Errorf("unsupported file type: %s", ext) } if err != nil { return nil, fmt.Errorf("error decoding image: %v", err)
M main.gomain.go

@@ -1,6 +1,7 @@

package main import ( + "context" "embed" "errors" "fmt"

@@ -8,13 +9,15 @@ "git.sophuwu.com/cdn/config"

"git.sophuwu.com/cdn/imgconv" "github.com/asdine/storm/v3" "go.etcd.io/bbolt" + "golang.org/x/sys/unix" + "os/signal" + pathlib "path" "io/fs" "time" _ "embed" "html/template" - "io" "net/http" "os" "path/filepath"

@@ -30,25 +33,23 @@ Short string

} type DirEntry struct { - Icon string - Name string - Size string - Url string - mod int64 - Mod TimeStr + Icon string + Name string + Size string + SizeN int64 + Url string + ModUnix 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 { +func FmtTime(t time.Time, today time.Time) 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") + pt.Full = t.Format(time.RFC822Z) + d := today.Sub(t) + if d < 5*24*time.Hour { + pt.Short = t.Format("Mon, 15:04") } else { - pt.Short = t.Format("02 Jan 2006") + pt.Short = t.Format("2006-01-02") } return pt }

@@ -73,17 +74,16 @@ }

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.SizeN = func() int64 { + n, _ := os.ReadDir(filepath.Join(config.HttpDir, a.Url)) + return int64(len(n)) }() + a.Size = fmt.Sprintf("%d items", a.SizeN) a.Icon = "F" t.Dirs = append(t.Dirs, a) } else { a.Icon = "f" + a.SizeN = size a.Size = Si(size) t.Items = append(t.Items, a) }

@@ -92,7 +92,7 @@ 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 { + if (*tt)[i].ModUnix < (*tt)[j].ModUnix { (*tt)[i], (*tt)[j] = (*tt)[j], (*tt)[i] } }

@@ -102,23 +102,31 @@ }

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", +type HttpCode struct { + Code int + Name string + Message string +} + +var HttpCodes = map[int]HttpCode{ + 404: HttpCode{404, "Not Found", "The file you requested was not found on this server."}, + 500: HttpCode{500, "Internal Server Error", "The server encountered an internal error and was unable to complete your request."}, + 403: HttpCode{403, "Forbidden", "The server understood the request, but is refusing to fulfill it."}, + 401: HttpCode{401, "Unauthorized", "You lack the necessary permissions to access this resource."}, + 400: HttpCode{400, "Bad Request", "The server could not understand the request as it was malformed."}, } -func FillError(w io.Writer, err error, code int) bool { +func FillError(w http.ResponseWriter, err error, code int) bool { if err == nil { return false } fmt.Fprintf(os.Stderr, "error: %s\n", err) - _ = Temp.ExecuteTemplate(w, "index", map[string]string{ - "Error": fmt.Sprintf("%d: %s", code, HttpCodes[code]), - }) + w.WriteHeader(code) + ht, ok := HttpCodes[code] + if !ok { + ht = HttpCodes[500] + } + _ = Temp.ExecuteTemplate(w, "error", ht) return true }

@@ -128,6 +136,22 @@ ModTime string

PngData []byte } +func CleanPath(d http.Dir, name string) (string, error) { + path := pathlib.Clean("/" + name)[1:] + if path == "" { + return "", errors.New("http: empty file path") + } + path, err := filepath.Localize(path) + if err != nil { + return "", errors.New("http: invalid or unsafe file path") + } + dir := string(d) + if !filepath.IsAbs(dir) { + return "", errors.New("http: invalid or unsafe file path") + } + return filepath.Join(dir, path), nil +} + func customFileServer(root http.Dir) http.Handler { iconFunc := func(w http.ResponseWriter, r *http.Request) { qq := r.URL.Query()

@@ -144,22 +168,12 @@ fn = DB.Save

} icon.ImgPath = qq.Get("icon") icon.ModTime = qq.Get("mod") - var f http.File - f, err = root.Open(icon.ImgPath) - if FillError(w, err, 404) { + var path string + if path, err = CleanPath(root, qq.Get("icon")); err != nil { + FillError(w, fmt.Errorf("icon or mod not found"), 400) return } - var fi fs.FileInfo - fi, err = f.Stat() - if err == nil && fi.IsDir() { - err = fmt.Errorf("icon is dir") - } - if FillError(w, err, 400) { - f.Close() - return - } - icon.PngData, err = imgconv.Media2Icon(icon.ImgPath, f) - f.Close() + icon.PngData, err = imgconv.Media2Icon(path) if FillError(w, err, 404) { return }

@@ -196,13 +210,13 @@ if FillError(w, err, 500) {

return } t := TemplateData{Path: upath, Dirs: []DirEntry{}, Items: []DirEntry{}} - today := DateToInt(time.Now()) + today := time.Now().UTC() 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), + Name: d.Name(), + Url: filepath.Join(upath, d.Name()), + ModUnix: d.ModTime().Local().Unix(), + Mod: FmtTime(d.ModTime().UTC(), today), }, d.Size(), d.IsDir()) } t.sortNewest()

@@ -216,13 +230,25 @@

var DB *storm.DB 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.Addr+":"+config.Port, nil) - + server := http.Server{ + Addr: config.Addr + ":" + config.Port, + Handler: customFileServer(http.Dir(config.HttpDir)), + } + fmt.Printf("starting cdn server with pid: %d\n\tlistening on %s\n\tserving directory: %s\n", os.Getpid(), server.Addr, config.HttpDir) + go func() { + if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + fmt.Fprintf(os.Stderr, "error: %s\n", err) + DB.Close() + os.Exit(1) + } + }() + sigChan := make(chan os.Signal) + signal.Notify(sigChan, unix.SIGINT, unix.SIGTERM) + fmt.Printf("got signal %v, stopping\n", <-sigChan) + server.Shutdown(context.Background()) + DB.Close() + fmt.Println("Server stopped") + os.Exit(0) } func init() {

@@ -233,6 +259,5 @@ if err != nil {

fmt.Println("Failed to open database:", err) os.Exit(1) } - db.Init(&ImgIcon{}) DB = db }