package server import ( "crypto/rand" "encoding/hex" "fmt" "net/http" "os" "path/filepath" "strings" "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" ) const uploadRoot = "data/uploads" func Run(addr string) error { router := gin.Default() router.LoadHTMLGlob("templates/*.html") router.GET("/", func(ctx *gin.Context) { ctx.HTML(http.StatusOK, "index.html", gin.H{}) }) router.POST("/upload", func(ctx *gin.Context) { form, err := ctx.MultipartForm() if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": "No files received"}) return } files := form.File["files"] if len(files) == 0 { ctx.JSON(http.StatusBadRequest, gin.H{"error": "No files received"}) return } boxID, err := newBoxID() if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create upload box"}) return } boxPath := filepath.Join(uploadRoot, boxID) if err := os.MkdirAll(boxPath, 0755); err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not prepare upload box"}) return } savedFiles := make([]gin.H, 0, len(files)) usedNames := make(map[string]int, len(files)) for _, file := range files { filename, ok := safeFilename(file.Filename) if !ok { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid filename"}) return } filename = uniqueFilename(filename, usedNames) destination := filepath.Join(boxPath, filename) if err := ctx.SaveUploadedFile(file, destination); err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not save uploaded files"}) return } savedFiles = append(savedFiles, gin.H{ "name": filename, "size": file.Size, }) } ctx.JSON(http.StatusOK, gin.H{ "box_id": boxID, "box_url": "/box/" + boxID, "files": savedFiles, }) }) compressed := router.Group("/", gzip.Gzip(gzip.DefaultCompression)) compressed.Static("/static", "./static") return router.Run(addr) } func newBoxID() (string, error) { bytes := make([]byte, 16) if _, err := rand.Read(bytes); err != nil { return "", err } return hex.EncodeToString(bytes), nil } func safeFilename(name string) (string, bool) { filename := filepath.Base(name) filename = strings.TrimSpace(filename) return filename, filename != "" && filename != "." && filename != string(filepath.Separator) } func uniqueFilename(filename string, usedNames map[string]int) string { count := usedNames[filename] usedNames[filename] = count + 1 if count == 0 { return filename } extension := filepath.Ext(filename) base := strings.TrimSuffix(filename, extension) return fmt.Sprintf("%s-%d%s", base, count+1, extension) }