Files
RenewCMS/api/handlers/staticimages/staticImagesHandler.go
T

94 lines
2.2 KiB
Go

package staticimages
import (
"RenewCMS/internal/domain/image"
"RenewCMS/internal/infrastructure/useCases"
"errors"
"io"
"net/http"
"path"
"strconv"
"strings"
"time"
"github.com/go-chi/chi/v5"
)
const cacheControl = "public, no-cache"
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 {
openMedia *useCases.OpenMediaUseCase
modTime time.Time
}
func NewHandler(openMedia *useCases.OpenMediaUseCase) *Handler {
return &Handler{
openMedia: openMedia,
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))
blob, err := h.openMedia.Open(cleaned)
if errors.Is(err, image.ErrBlobNotFound) {
http.NotFound(w, r)
return
}
if err != nil {
http.Error(w, "Unable to read the file", http.StatusInternalServerError)
return
}
defer blob.Content.Close()
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)
}
if content, ok := blob.Content.(io.ReadSeeker); ok {
http.ServeContent(w, r, cleaned, blob.ModTime, content)
return
}
w.Header().Set("Content-Length", strconv.FormatInt(blob.Size, 10))
if !blob.ModTime.IsZero() {
w.Header().Set("Last-Modified", blob.ModTime.UTC().Format(http.TimeFormat))
}
w.WriteHeader(http.StatusOK)
if r.Method != http.MethodHead {
_, _ = io.Copy(w, blob.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 contentTypeFor(p string) string {
ext := path.Ext(p)
if contentType, ok := contentTypeExtensions[ext]; ok {
return contentType
}
return fallbackContentType
}