package api import ( "bytes" "encoding/base64" "github.com/gin-gonic/gin" "log" "net/http" "resource-manager/domain/resize" "resource-manager/domain/resource" "strings" "time" ) type UploadRequest struct { Content string `json:"content"` Path string `json:"path"` Properties struct { Overwrite bool `json:"overwrite"` MimeType string `json:"mimeType"` } `json:"properties"` Resize *struct { Width int `json:"width"` Height int `json:"height"` Type string `json:"type"` } `json:"resize"` } func NoMethod() gin.HandlerFunc { return func(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{ "status": 404, "created": time.Now(), "message": "no handler for method '" + c.Request.Method + "'", }) } } func NoRoute() gin.HandlerFunc { return func(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{ "status": 404, "created": time.Now(), "message": "no handler for " + c.Request.Method + " '" + c.Request.URL.RequestURI() + "'", }) } } func HandleUpload(resourceManager resource.Manager) gin.HandlerFunc { return func(c *gin.Context) { var request UploadRequest if err := c.BindJSON(&request); err != nil { c.AbortWithStatusJSON(400, gin.H{"error": "bad request"}) return } if !isValidPath(request.Path) { c.AbortWithStatusJSON(400, gin.H{"error": "path cannot start with ../ or contain /../"}) return } content, err := base64.StdEncoding.DecodeString(request.Content) if err != nil { c.AbortWithStatusJSON(400, gin.H{"error": "bad request"}) return } if resize.IsResizable(readMimeType(request.Path, request.Properties.MimeType)) && request.Resize != nil { content, err = resize.ResizeImage(content, resize.Resize{ Height: request.Resize.Height, Width: request.Resize.Width, Type: mapResizeType(request.Resize.Type), }) if err != nil { c.AbortWithStatusJSON(400, gin.H{"error": "bad request"}) return } } log.Printf("Uploading '%s'...", request.Path) resourceManager.Upload(resource.UploadRequest{ Buffer: bytes.NewBuffer(content), Path: request.Path, MimeType: request.Properties.MimeType, Overwrite: request.Properties.Overwrite, }) log.Printf("Uploaded '%s'", request.Path) // we return this as success c.Status(http.StatusNoContent) } } func isValidPath(currentPath string) bool { if strings.HasPrefix(currentPath, "../") || strings.Contains(currentPath, "/../") { return false } return true } func HandleCopy(resourceManager resource.Manager) gin.HandlerFunc { return func(c *gin.Context) { from := c.Query("from") to := c.Query("to") overwrite := c.Query("overwrite") == "true" log.Printf("Copying from '%s' to '%s' (overwrite=%v)...", from, to, overwrite) if err := resourceManager.Copy(from, to, overwrite); err != nil { log.Println(err) c.AbortWithStatus(500) } else { log.Printf("Done copyting from '%s' to '%s' (overwrite=%v)", from, to, overwrite) c.Status(201) } } } func HandlePresign(resourceManager resource.Manager) gin.HandlerFunc { return func(c *gin.Context) { path := c.Query("path") if !isValidPath(path) { c.AbortWithStatusJSON(400, gin.H{"error": "path cannot start with ../ or contain /../"}) return } url, err := resourceManager.Presign(c, path) if err != nil { c.AbortWithStatus(404) return } log.Printf("Presigned '%s'", path) if c.Query("redirect") == "true" { c.Redirect(http.StatusTemporaryRedirect, url) } else { c.JSON(200, gin.H{"url": url}) } } } func HandleDownload(resourceManager resource.Manager) gin.HandlerFunc { return func(c *gin.Context) { path := "" if c.GetString("secure") == "token" { path = c.GetString("path") } else { path = c.Query("path") } if !isValidPath(path) { c.AbortWithStatusJSON(400, gin.H{"error": "path cannot start with ../ or contain /../"}) return } log.Printf("Downloading '%s'...", path) data, err := resourceManager.Download(c, path) if err == nil { c.Header("content-disposition", "inline; filename=\""+filename(path)+"\"") c.Data(200, readMimeType(path, ""), data) log.Printf("Downloaded '%s'", path) } else { c.AbortWithStatus(http.StatusNotFound) } } } func HandleDelete(resourceManager resource.Manager) gin.HandlerFunc { return func(c *gin.Context) { path := c.Query("path") if !isValidPath(path) { c.AbortWithStatusJSON(400, gin.H{"error": "path cannot start with ../ or contain /../"}) return } log.Printf("Deleting '%s'...", path) if err := resourceManager.Delete(path); err != nil { _ = c.AbortWithError(400, err) } else { log.Printf("Deleted '%s'", path) c.Status(204) } } } var mimeTypes = map[string]string{ "jpg": "image/jpg", "jpeg": "image/jpeg", "png": "image/png", "gif": "image/gif", "json": "application/json", "pdf": "application/pdf", "html": "text/html", "xml": "application/xml", } func readMimeType(path string, mimeType string) string { if mimeType != "" { return mimeType } parts := strings.Split(path, ".") fileType := strings.ToLower(parts[len(parts)-1]) if value, exists := mimeTypes[fileType]; exists { return value } return "application/octet-stream" } func mapResizeType(resizeType string) resize.ResizeType { switch resizeType { case "cover": return resize.Cover case "contain": return resize.Contain case "exact_height": return resize.ExactHeight case "exact_width": return resize.ExactWidth default: return resize.Exact } } func filename(path string) string { substrings := strings.Split(path, "/") return substrings[len(substrings)-1] }