package main import ( "crypto/rand" "encoding/hex" "fmt" "html" "io" "log" "net" "net/http" "net/url" "os" "path/filepath" "strconv" "strings" ) func main() { home, _ := os.UserHomeDir() cfgDir := filepath.Join(home, ".file-share") _ = os.MkdirAll(cfgDir, 0o755) cfgPath := filepath.Join(cfgDir, "receiver.env") dropDir := env("DROP_DIR", filepath.Join(home, "Drops")) port := env("PORT", "8787") host := env("HOST", lanAddress()) authToken := env("AUTH_TOKEN", "") if authToken == "" { authToken = loadOrGenerateToken(cfgPath) } _ = os.MkdirAll(dropDir, 0o755) log.Printf("drop dir: %s", dropDir) log.Printf("auth token: %s", authToken) log.Printf("listening on http://%s:%s", host, port) mux := http.NewServeMux() mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "ok") }) mux.HandleFunc("POST /upload", handleUpload(dropDir, authToken)) mux.HandleFunc("GET /files", handleList(dropDir)) mux.HandleFunc("GET /download", handleDownload(dropDir)) log.Fatal(http.ListenAndServe(host+":"+port, mux)) } func handleUpload(dropDir, authToken string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if authToken != "" && r.Header.Get("X-Auth") != authToken { http.Error(w, "unauthorized", http.StatusUnauthorized) return } if err := r.ParseMultipartForm(32 << 20); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } files := r.MultipartForm.File["file"] if len(files) == 0 { http.Error(w, "no file", http.StatusBadRequest) return } type savedFile struct { Saved string `json:"saved"` } saved := make([]savedFile, 0, len(files)) for _, fh := range files { src, err := fh.Open() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } dest := uniquePath(dropDir, filepath.Base(fh.Filename)) dst, err := os.Create(dest) if err != nil { _ = src.Close() http.Error(w, err.Error(), http.StatusInternalServerError) return } n, err := io.Copy(dst, src) _ = src.Close() _ = dst.Close() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } name := filepath.Base(dest) log.Printf("saved %s (%d bytes) from %s", name, n, r.RemoteAddr) saved = append(saved, savedFile{Saved: name}) } w.Header().Set("Content-Type", "application/json") if len(saved) == 1 { fmt.Fprintf(w, `{"saved":%q}`, saved[0].Saved) return } fmt.Fprint(w, "[") for i, s := range saved { if i > 0 { fmt.Fprint(w, ",") } fmt.Fprintf(w, `{"saved":%q}`, s.Saved) } fmt.Fprint(w, "]") } } func handleList(dropDir string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { entries, err := os.ReadDir(dropDir) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprint(w, `