diff --git a/.air.toml b/.air.toml index 932ef5d..fb86af2 100644 --- a/.air.toml +++ b/.air.toml @@ -2,7 +2,7 @@ root = "." tmp_dir = "tmp" [build] -args_bin = ["--port", "4321"] +args_bin = ["--port", "4321", "--path", "~/Downloads/portal"] bin = "./tmp/main" cmd = "go build -o ./tmp/main ." delay = 1000 @@ -13,7 +13,7 @@ exclude_unchanged = false follow_symlink = false full_bin = "" include_dir = [".", "public"] -include_ext = ["go", "html", "css", "js"] +include_ext = ["go", "html", "css", "js", "webp", "svg"] include_file = ["main.go"] kill_delay = "1s" log = "build-errors.log" diff --git a/main.go b/main.go index ec4e1b5..41a6a46 100644 --- a/main.go +++ b/main.go @@ -9,14 +9,26 @@ import ( "net" "net/http" "os" + "path/filepath" + "time" ) const ( chunkSize = 1024 * 1024 // 1MB ) -var upgrader = websocket.Upgrader{ - ReadBufferSize: chunkSize, +var ( + upgrader = websocket.Upgrader{ + ReadBufferSize: chunkSize, + } + wd string +) + +func init() { + var err error + if wd, err = os.Getwd(); err != nil { + log.Fatal("failed to get working directory", "err", err) + } } type ( @@ -25,6 +37,8 @@ type ( Name string `json:"name"` // Size is the size of the file in bytes. Size int `json:"size"` + // LastModified is the last modified time of the file. + LastModified int64 `json:"lastModified"` } ) @@ -45,12 +59,27 @@ func wsHandler(w http.ResponseWriter, r *http.Request) { } log.Info("received header", "header", header) - file, err := os.Create(header.Name) + p := filepath.Join(wd, header.Name) + + // check if file is inside wd + if relPath, err := filepath.Rel(wd, p); err != nil || relPath == ".." || relPath[:2] == ".." { + log.Error("file is outside working directory", "path", p) + return + } + + // create parent directories + if err := os.MkdirAll(filepath.Dir(p), 0777); err != nil { + log.Error("failed to create parent directories", "err", err) + return + } + + // create file + f, err := os.Create(p) if err != nil { log.Error("failed to create file", "err", err) return } - defer file.Close() + defer f.Close() // Send READY signal to start receiving file chunks err = conn.WriteMessage(websocket.TextMessage, []byte("READY")) @@ -76,7 +105,7 @@ func wsHandler(w http.ResponseWriter, r *http.Request) { } if messageType == websocket.BinaryMessage { - _, err = file.Write(p) + _, err = f.Write(p) if err != nil { log.Error("failed to write to file", "err", err) return @@ -90,6 +119,12 @@ func wsHandler(w http.ResponseWriter, r *http.Request) { } } + // set last modified time + lastModified := time.UnixMilli(header.LastModified) + if err := os.Chtimes(p, lastModified, lastModified); err != nil { + log.Error("failed to set last modified time", "err", err) + } + // send EOF to client to signal that the file has been received err = conn.WriteMessage(websocket.TextMessage, []byte("EOF")) if err != nil { @@ -119,28 +154,26 @@ func getPublicIP() (string, error) { func main() { port := flag.Int("port", 0, "port to listen on") + path := flag.String("path", ".", "path to save files") flag.Parse() - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/backdrop.webp": - w.Header().Set("Content-Type", "image/webp") - _, _ = w.Write(public.BackdropWebP) - case "/favicon.svg": - w.Header().Set("Content-Type", "image/svg+xml") - _, _ = w.Write(public.FaviconSVG) - case "/index.css": - w.Header().Set("Content-Type", "text/css") - _, _ = w.Write(public.IndexCSS) - case "/": - w.Header().Set("Content-Type", "text/html") - _, _ = w.Write(public.IndexHTML) - case "/index.js": - w.Header().Set("Content-Type", "application/javascript") - _, _ = w.Write(public.IndexJS) - default: - http.NotFound(w, r) + if *path != "." { + if err := os.MkdirAll(*path, 0777); err != nil { + log.Fatal("failed to create directory", "path", path, "err", err) + return + } + if err := os.Chdir(*path); err != nil { + log.Fatal("failed to change directory", "err", err) + return + } + var err error + if wd, err = os.Getwd(); err == nil { + log.Info("working directory", "path", wd) } + } + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.FileServerFS(public.Fs).ServeHTTP(w, r) }) http.HandleFunc("/ws", wsHandler) diff --git a/public/favicon.svg b/public/favicon.svg index a7a21c3..2702211 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,2 +1,2 @@ - \ No newline at end of file + diff --git a/public/file-earmark-binary.svg b/public/file-earmark-binary.svg new file mode 100644 index 0000000..8b4d1d3 --- /dev/null +++ b/public/file-earmark-binary.svg @@ -0,0 +1,4 @@ + diff --git a/public/file-earmark-code.svg b/public/file-earmark-code.svg new file mode 100644 index 0000000..5ca896e --- /dev/null +++ b/public/file-earmark-code.svg @@ -0,0 +1,4 @@ + diff --git a/public/file-earmark-font.svg b/public/file-earmark-font.svg new file mode 100644 index 0000000..4c4e9fb --- /dev/null +++ b/public/file-earmark-font.svg @@ -0,0 +1,4 @@ + diff --git a/public/file-earmark-image.svg b/public/file-earmark-image.svg new file mode 100644 index 0000000..fdf8a68 --- /dev/null +++ b/public/file-earmark-image.svg @@ -0,0 +1,4 @@ + diff --git a/public/file-earmark-music.svg b/public/file-earmark-music.svg new file mode 100644 index 0000000..3867db2 --- /dev/null +++ b/public/file-earmark-music.svg @@ -0,0 +1,4 @@ + diff --git a/public/file-earmark-pdf.svg b/public/file-earmark-pdf.svg new file mode 100644 index 0000000..2e2b282 --- /dev/null +++ b/public/file-earmark-pdf.svg @@ -0,0 +1,4 @@ + diff --git a/public/file-earmark-play.svg b/public/file-earmark-play.svg new file mode 100644 index 0000000..3cd35d4 --- /dev/null +++ b/public/file-earmark-play.svg @@ -0,0 +1,4 @@ + diff --git a/public/file-earmark-text.svg b/public/file-earmark-text.svg new file mode 100644 index 0000000..1f6157d --- /dev/null +++ b/public/file-earmark-text.svg @@ -0,0 +1,4 @@ + diff --git a/public/file-earmark.svg b/public/file-earmark.svg new file mode 100644 index 0000000..5400b1c --- /dev/null +++ b/public/file-earmark.svg @@ -0,0 +1,3 @@ + diff --git a/public/index.css b/public/index.css index 71e217b..e43dd80 100644 --- a/public/index.css +++ b/public/index.css @@ -46,13 +46,14 @@ main { main::before { content: "Drop files here"; + filter: drop-shadow(0 0 1rem black); position: fixed; display: block; - font-family: "Garamond", "Bookman Old Style", "Georgia", "Times New Roman", serif; + font-family: "Fondamento", "Garamond", "Bookman Old Style", "Georgia", "Times New Roman", cursive; top: 5lvh; left: 0; width: 100dvw; - font-size: 2rem; + font-size: 3rem; font-weight: bold; text-align: center; } @@ -61,7 +62,7 @@ main::before { content: "Sending..."; } -.ring, .particle, main::before { +.ring, .particle, .file, main::before { pointer-events: none; } @@ -135,7 +136,7 @@ main::before { } .sending .ring { - background: radial-gradient(closest-side, pink, rgba(var(--magic), 1)); + background: radial-gradient(closest-side, rgba(255, 200, 230, 0.25), rgba(var(--magic), 1)); } .particle { @@ -162,7 +163,7 @@ main::before { opacity: 1; } 100% { - transform: translate(50vmin, 10vmin) scale(1); + transform: translate(50vmax, 10vmax) scale(1); opacity: 0; } } @@ -176,7 +177,7 @@ main::before { opacity: 1; } 100% { - transform: translate(-50vmin, -13vmin) scale(1); + transform: translate(-50vmax, -13vmax) scale(1); opacity: 0; } } @@ -190,7 +191,7 @@ main::before { opacity: 1; } 100% { - transform: translate(-10vmin, 50vmin) scale(1); + transform: translate(-10vmax, 50vmax) scale(1); opacity: 0; } } @@ -204,7 +205,7 @@ main::before { opacity: 1; } 100% { - transform: translate(13vmin, -50vmin) scale(1); + transform: translate(13vmax, -50vmax) scale(1); opacity: 0; } } @@ -218,7 +219,7 @@ main::before { opacity: 1; } 100% { - transform: translate(-2vmin, -40vmin) scale(1); + transform: translate(-10vmax, -25vmax) scale(1); opacity: 0; } } @@ -232,7 +233,7 @@ main::before { opacity: 1; } 100% { - transform: translate(40vmin, 2vmin) scale(1); + transform: translate(25vmax, 10vmax) scale(1); opacity: 0; } } @@ -246,7 +247,7 @@ main::before { opacity: 1; } 100% { - transform: translate(-40vmin, -2vmin) scale(1); + transform: translate(-25vmax, -10vmax) scale(1); opacity: 0; } } @@ -260,7 +261,7 @@ main::before { opacity: 1; } 100% { - transform: translate(2vmin, 40vmin) scale(1); + transform: translate(10vmax, 25vmax) scale(1); opacity: 0; } } @@ -306,11 +307,38 @@ main::before { } .particle { + animation-direction: normal; animation-duration: 3s; } +.drag-over .particle { + animation-direction: reverse; + animation-duration: 2s; +} .sending .particle { animation-direction: reverse; animation-duration: 1s; } + +#file-icons { + z-index: 1000; + position: fixed; + top: 0; + left: 0; + padding: min(10vmin, 128px); + width: 100lvw; + height: 100lvh; + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + gap: 1vmin; + pointer-events: none; +} + +#file-icons .file { + display: block; + width: 5vmin; + aspect-ratio: auto; +} diff --git a/public/index.html b/public/index.html index 826792b..3913299 100644 --- a/public/index.html +++ b/public/index.html @@ -3,14 +3,18 @@
-