refac: GET /static/uploadedImages/{filename}

This commit is contained in:
fsylvain committed 2026-09-25 15:40:21 +02:00
1 parent 8d17a12e92
commit 61828783a1
5 files changed
+592

No files matched your search

@@ -0,0 +1,98 @@
package staticimages
import (
"io"
"io/fs"
"net/http"
"os"
"path"
"strings"
"time"
"github.com/go-chi/chi/v5"
)
const cacheControl = "public, max-age=31536000, immutable"
const svgContentSecurityPolicy = "default-src 'none'; style-src 'unsafe-inline'; sandbox"
const fallbackContentType = "application/octet-stream"
var contentTypeExtensions = map[string]string{
".png": "image/png",
".jpeg": "image/jpeg",
".webp": "image/webp",
".svg": "image/svg+xml",
}
type Handler struct {
uploadDir fs.FS
modTime time.Time
}
func NewHandler(uploadDir string) *Handler {
return &Handler{
uploadDir: os.DirFS(uploadDir),
modTime: time.Now(),
}
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
cleaned := cleanPath(requestedPath(r))
file, stat, ok := h.openFile(cleaned)
if !ok {
http.NotFound(w, r)
return
}
defer file.Close()
content, ok := file.(io.ReadSeeker)
if !ok {
http.Error(w, "File not seekable", http.StatusInternalServerError)
return
}
contentType := contentTypeFor(cleaned)
w.Header().Set("Content-Type", contentType)
w.Header().Set("Cache-Control", cacheControl)
if contentType == "image/svg+xml" {
w.Header().Set("Content-Security-Policy", svgContentSecurityPolicy)
}
http.ServeContent(w, r, cleaned, stat.ModTime(), content)
}
func requestedPath(r *http.Request) string {
if rctx := chi.RouteContext(r.Context()); rctx != nil && rctx.RoutePath != "" {
return rctx.RoutePath
}
return r.URL.Path
}
func cleanPath(p string) string {
return strings.TrimPrefix(path.Clean("/"+p), "/")
}
func (h *Handler) openFile(p string) (fs.File, fs.FileInfo, bool) {
if p == "" || p == "." {
return nil, nil, false
}
file, err := h.uploadDir.Open(p)
if err != nil {
return nil, nil, false
}
stat, err := file.Stat()
if err != nil || stat.IsDir() {
file.Close()
return nil, nil, false
}
return file, stat, true
}
func contentTypeFor(p string) string {
ext := path.Ext(p)
if contentType, ok := contentTypeExtensions[ext]; ok {
return contentType
}
return fallbackContentType
}
+2
View File
@@ -5,6 +5,7 @@ import (
"RenewCMS/api/handlers/auth"
"RenewCMS/api/handlers/frontend"
"RenewCMS/api/handlers/image"
"RenewCMS/api/handlers/staticimages"
"RenewCMS/api/middleware"
"RenewCMS/internal/infrastructure/config"
"encoding/json"
@@ -139,6 +140,7 @@ func InitRoutes(container *UseCases, settings config.Settings) *chi.Mux {
apiRouter.Use(chimiddleware.GetHead)
apiRouter.Get("/healthz", GetHealth)
apiRouter.Mount("/v1", backend)
apiRouter.Mount("/static/uploadedImages", staticimages.NewHandler(settings.UploadDir))
apiRouter.Mount("/", frontend)
return apiRouter