2024-01-03 19:56:42 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/base64"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
2024-01-03 20:25:15 +00:00
|
|
|
"resource-manager/domain/resize"
|
|
|
|
"resource-manager/domain/resource"
|
2024-01-03 19:56:42 +00:00
|
|
|
"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
|
|
|
|
}
|
2024-01-07 19:23:15 +00:00
|
|
|
if !isValidPath(request.Path) {
|
|
|
|
c.AbortWithStatusJSON(400, gin.H{"error": "path cannot start with ../ or contain /../"})
|
|
|
|
return
|
|
|
|
}
|
2024-01-03 19:56:42 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2024-01-04 21:05:02 +00:00
|
|
|
log.Printf("Uploading '%s'...", request.Path)
|
|
|
|
|
2024-01-03 19:56:42 +00:00
|
|
|
resourceManager.Upload(resource.UploadRequest{
|
|
|
|
Buffer: bytes.NewBuffer(content),
|
|
|
|
Path: request.Path,
|
|
|
|
MimeType: request.Properties.MimeType,
|
|
|
|
Overwrite: request.Properties.Overwrite,
|
|
|
|
})
|
2024-01-04 21:05:02 +00:00
|
|
|
log.Printf("Uploaded '%s'", request.Path)
|
2024-01-03 19:56:42 +00:00
|
|
|
// we return this as success
|
|
|
|
c.Status(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-07 19:23:15 +00:00
|
|
|
func isValidPath(currentPath string) bool {
|
|
|
|
if strings.HasPrefix(currentPath, "../") || strings.Contains(currentPath, "/../") {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-01-03 19:56:42 +00:00
|
|
|
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"
|
2024-01-04 21:05:02 +00:00
|
|
|
log.Printf("Copying from '%s' to '%s' (overwrite=%v)...", from, to, overwrite)
|
2024-01-03 19:56:42 +00:00
|
|
|
if err := resourceManager.Copy(from, to, overwrite); err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
c.AbortWithStatus(500)
|
|
|
|
} else {
|
2024-01-04 21:05:02 +00:00
|
|
|
log.Printf("Done copyting from '%s' to '%s' (overwrite=%v)", from, to, overwrite)
|
2024-01-03 19:56:42 +00:00
|
|
|
c.Status(201)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func HandlePresign(resourceManager resource.Manager) gin.HandlerFunc {
|
|
|
|
return func(c *gin.Context) {
|
|
|
|
path := c.Query("path")
|
2024-01-07 19:23:15 +00:00
|
|
|
if !isValidPath(path) {
|
|
|
|
c.AbortWithStatusJSON(400, gin.H{"error": "path cannot start with ../ or contain /../"})
|
|
|
|
return
|
|
|
|
}
|
2024-01-03 19:56:42 +00:00
|
|
|
url, err := resourceManager.Presign(c, path)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithStatus(404)
|
|
|
|
return
|
|
|
|
}
|
2024-01-04 21:05:02 +00:00
|
|
|
log.Printf("Presigned '%s'", path)
|
|
|
|
|
2024-01-03 19:56:42 +00:00
|
|
|
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) {
|
2024-01-06 13:10:24 +00:00
|
|
|
path := ""
|
|
|
|
if c.GetString("secure") == "token" {
|
|
|
|
path = c.GetString("path")
|
|
|
|
} else {
|
|
|
|
path = c.Query("path")
|
|
|
|
}
|
2024-01-07 19:23:15 +00:00
|
|
|
if !isValidPath(path) {
|
|
|
|
c.AbortWithStatusJSON(400, gin.H{"error": "path cannot start with ../ or contain /../"})
|
|
|
|
return
|
|
|
|
}
|
2024-01-04 21:05:02 +00:00
|
|
|
log.Printf("Downloading '%s'...", path)
|
2024-01-03 19:56:42 +00:00
|
|
|
data, err := resourceManager.Download(c, path)
|
|
|
|
if err == nil {
|
|
|
|
c.Header("content-disposition", "inline; filename=\""+filename(path)+"\"")
|
|
|
|
c.Data(200, readMimeType(path, ""), data)
|
2024-01-06 13:10:24 +00:00
|
|
|
log.Printf("Downloaded '%s'", path)
|
2024-01-03 19:56:42 +00:00
|
|
|
} else {
|
|
|
|
c.AbortWithStatus(http.StatusNotFound)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func HandleDelete(resourceManager resource.Manager) gin.HandlerFunc {
|
|
|
|
return func(c *gin.Context) {
|
|
|
|
path := c.Query("path")
|
2024-01-07 19:23:15 +00:00
|
|
|
if !isValidPath(path) {
|
|
|
|
c.AbortWithStatusJSON(400, gin.H{"error": "path cannot start with ../ or contain /../"})
|
|
|
|
return
|
|
|
|
}
|
2024-01-04 21:05:02 +00:00
|
|
|
log.Printf("Deleting '%s'...", path)
|
2024-01-03 19:56:42 +00:00
|
|
|
if err := resourceManager.Delete(path); err != nil {
|
2024-01-06 13:17:22 +00:00
|
|
|
_ = c.AbortWithError(400, err)
|
2024-01-03 19:56:42 +00:00
|
|
|
} else {
|
2024-01-04 21:05:02 +00:00
|
|
|
log.Printf("Deleted '%s'", path)
|
2024-01-03 19:56:42 +00:00
|
|
|
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]
|
|
|
|
}
|