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 }