From 25d607e68c6d5b3616c38ebdb48b81a7a4ef1187 Mon Sep 17 00:00:00 2001 From: visionmercer <62051836+visionmercer@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:56:24 +0200 Subject: [PATCH] added safety and color --- internal/handlers/handlers.go | 41 +++++++++++++++- static/css/style.css | 41 ++++++++++------ templates/browse.html | 88 +++++++++++++++++++++++++---------- 3 files changed, 129 insertions(+), 41 deletions(-) diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index c1801a9..4d0101c 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -205,7 +205,36 @@ func (h *Handler) DownloadFile(w http.ResponseWriter, r *http.Request) { func (h *Handler) RawFile(w http.ResponseWriter, r *http.Request) { fileName := strings.TrimPrefix(r.URL.Path, "/raw/") - http.ServeFile(w, r, filepath.Join(h.dir, fileName)) + + // Security: Prevent path traversal attacks + if fileName == "" || strings.Contains(fileName, "..") || strings.HasPrefix(fileName, "/") { + http.Error(w, "Invalid path", http.StatusBadRequest) + return + } + + // Clean the path to prevent any directory traversal + cleanPath := filepath.Clean(fileName) + if strings.Contains(cleanPath, "..") { + http.Error(w, "Invalid path", http.StatusBadRequest) + return + } + + fullPath := filepath.Join(h.dir, cleanPath) + + // Verify the file exists and is within the allowed directory + fileInfo, err := os.Stat(fullPath) + if err != nil { + http.Error(w, "File not found", http.StatusNotFound) + return + } + + // Ensure it's not a directory + if fileInfo.IsDir() { + http.Error(w, "Not found", http.StatusNotFound) + return + } + + http.ServeFile(w, r, fullPath) } func (h *Handler) ServeStatic(w http.ResponseWriter, r *http.Request) { @@ -318,10 +347,18 @@ func fileIcon(name string) string { return "💿" case ".pdf": return "📄" - case ".jpg", ".png", ".jpeg": + case ".jpg", ".png", ".jpeg", ".gif", ".pcx", ".bmp": return "🖼️" case ".txt", ".md": return "📝" + case ".mp3", ".mod", ".ogg", ".wav", ".flac": + return "🎵" + case ".mp4", ".avi", ".mov": + return "🎬" + case ".zip", ".rar", ".7z": + return "📦" + case ".exe", ".msi": + return "📦" default: return "📄" } diff --git a/static/css/style.css b/static/css/style.css index 1d2015b..77425fc 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,23 +1,26 @@ :root { /* Gruvbox Dark Palette */ - --bg: #282828; /* dark0 */ - --surface: #3c3836; /* dark1 */ - --border: #504945; /* dark2 */ - --accent: #fabd2f; /* yellow */ - --text: #ebdbb2; /* light1 */ - --muted: #a89984; /* gray */ - --radius: 0px; /* Forced to 0 for sharp corners */ + --bg: #282828; /* dark0 */ + --surface: #3c3836; /* dark1 */ + --border: #504945; /* dark2 */ + --accent: #fabd2f; /* yellow */ + --text: #ebdbb2; /* light1 */ + --muted: #a89984; /* gray */ + --radius: 0px; /* Forced to 0 for sharp corners */ } body { background: var(--bg); color: var(--text); - font-family: system-ui, -apple-system, sans-serif; + font-family: + system-ui, + -apple-system, + sans-serif; margin: 0; } header { - background: #1d2021; /* dark0_hard */ + background: #1d2021; /* dark0_hard */ border-bottom: 1px solid var(--border); padding: 0 2rem; height: 56px; @@ -26,7 +29,7 @@ header { } .logo { - color: var(--accent); + color: var(--text); font-weight: bold; text-decoration: none; font-family: monospace; @@ -95,7 +98,7 @@ main { .card-name { font-weight: bold; - color: var(--accent); + color: var(--text); margin-bottom: 0.5rem; display: block; overflow: hidden; @@ -103,6 +106,10 @@ main { white-space: nowrap; } +.card-name:hover { + color: var(--accent); +} + .card-desc { font-size: 0.8rem; color: var(--muted); @@ -120,10 +127,14 @@ main { } .bc a { - color: var(--accent); + color: var(--text); text-decoration: none; } +.bc a:hover { + color: var(--accent); +} + .tbl { width: 100%; border-collapse: collapse; @@ -159,7 +170,9 @@ main { cursor: pointer; display: inline-flex; align-items: center; - transition: background 0.2s, color 0.2s; + transition: + background 0.2s, + color 0.2s; } .btn:hover { @@ -198,7 +211,7 @@ main { max-height: 80vh; overflow-y: auto; position: relative; - box-shadow: 0 10px 30px rgba(0,0,0,0.5); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); } .modal-header { diff --git a/templates/browse.html b/templates/browse.html index 297df03..04016a8 100644 --- a/templates/browse.html +++ b/templates/browse.html @@ -1,27 +1,65 @@ - + -{{.Title}} - -
-
- - - - - {{range .Entries}} - - - - - - {{end}} - -
NameSizeAction
- {{if .IsDir}}📁 {{.Name}} - {{else}}{{fileIcon .Name}} {{.Name}}{{end}} - {{if .IsDir}}—{{else}}{{humanSize .Size}}{{end}}{{if not .IsDir}}Download{{end}}
-
- + + {{.Title}} + + + +
+
+ + + + + + + + + + + {{range .Entries}} + + + + + + {{end}} + +
NameSizeAction
+ {{if .IsDir}}📁 + {{.Name}} + {{else}}{{fileIcon .Name}} + {{.Name}}{{end}} + + {{if .IsDir}}—{{else}}{{humanSize .Size}}{{end}} + + {{if not .IsDir}}Download{{end}} +
+
+