From 186747e7e8271231e79bc816129faa9bbd724d3f Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 30 May 2026 23:46:09 -0700 Subject: [PATCH] admin: view images and PDFs inline in the file browser (#9751) The viewer embedded images and PDFs through the download URL, which sent Content-Disposition: attachment, so the browser downloaded them instead of rendering. Add an inline mode to the download endpoint, limited to images and PDFs so a hostile upload (HTML, SVG) can't run as same-origin script, set X-Content-Type-Options: nosniff, and resolve the MIME the same way the viewer does. The viewer now requests the inline URL. --- weed/admin/handlers/file_browser_grpc.go | 21 ++++++++++++-------- weed/admin/handlers/file_browser_handlers.go | 3 ++- weed/admin/static/js/admin.js | 4 ++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/weed/admin/handlers/file_browser_grpc.go b/weed/admin/handlers/file_browser_grpc.go index 9baf51d10..f357fe480 100644 --- a/weed/admin/handlers/file_browser_grpc.go +++ b/weed/admin/handlers/file_browser_grpc.go @@ -9,8 +9,10 @@ import ( "net/http" "path" "strconv" + "strings" "time" + "github.com/seaweedfs/seaweedfs/weed/admin/dash" "github.com/seaweedfs/seaweedfs/weed/filer" "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/operation" @@ -67,7 +69,7 @@ func (h *FileBrowserHandlers) fetchFileContentGrpc(ctx context.Context, filePath // response writer receives the canonical attachment headers and the raw // bytes; this replaces the HTTP-to-filer proxy that used to run in // DownloadFile. -func (h *FileBrowserHandlers) downloadFileGrpc(ctx context.Context, filePath string, w http.ResponseWriter) error { +func (h *FileBrowserHandlers) downloadFileGrpc(ctx context.Context, filePath string, w http.ResponseWriter, inline bool) error { cleanFilePath, err := h.validateAndCleanFilePath(filePath) if err != nil { return err @@ -84,16 +86,19 @@ func (h *FileBrowserHandlers) downloadFileGrpc(ctx context.Context, filePath str size := int64(filer.FileSize(entry)) fileName := path.Base(cleanFilePath) - w.Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": fileName})) - contentType := "" - if entry.Attributes != nil { - contentType = entry.Attributes.Mime - } - if contentType == "" { - contentType = "application/octet-stream" + // Resolve mime like the viewer does so Content-Type and the inline check agree. + contentType := dash.ResolveEntryMime(entry) + + // Only inline images and PDFs; serve the rest as attachments so a hostile upload + // (HTML, SVG) can't run as same-origin script. nosniff locks the declared type. + disposition := "attachment" + if inline && (strings.HasPrefix(contentType, "image/") || contentType == "application/pdf") { + disposition = "inline" } + w.Header().Set("Content-Disposition", mime.FormatMediaType(disposition, map[string]string{"filename": fileName})) w.Header().Set("Content-Type", contentType) + w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) w.WriteHeader(http.StatusOK) diff --git a/weed/admin/handlers/file_browser_handlers.go b/weed/admin/handlers/file_browser_handlers.go index 20893afdd..b26f0cd4d 100644 --- a/weed/admin/handlers/file_browser_handlers.go +++ b/weed/admin/handlers/file_browser_handlers.go @@ -397,8 +397,9 @@ func (h *FileBrowserHandlers) DownloadFile(w http.ResponseWriter, r *http.Reques writeJSONError(w, http.StatusBadRequest, "File path is required") return } + inline := r.URL.Query().Get("inline") == "true" tracker := &responseWriteTracker{ResponseWriter: w} - if err := h.downloadFileGrpc(r.Context(), filePath, tracker); err != nil { + if err := h.downloadFileGrpc(r.Context(), filePath, tracker, inline); err != nil { // Once bytes have been written we can't switch to a JSON error body // without corrupting the partial response — log and stop. Before any // write the response is still uncommitted, so a 502 with details is diff --git a/weed/admin/static/js/admin.js b/weed/admin/static/js/admin.js index 257c89a0d..f93d8315e 100644 --- a/weed/admin/static/js/admin.js +++ b/weed/admin/static/js/admin.js @@ -1788,7 +1788,7 @@ function createFileViewerContent(file, content) { if (file.mime.startsWith('image/')) { return `
- ${file.name}
`; @@ -1806,7 +1806,7 @@ function createFileViewerContent(file, content) { } else if (file.mime === 'application/pdf') { return `
-
`;