From 1bfefe079e8f03d44ec22b85f77c91f6abb2e902 Mon Sep 17 00:00:00 2001 From: fen Date: Thu, 10 Sep 2026 14:16:59 -0500 Subject: [PATCH 1/2] #221: image paste view fixes - image pastes scale to fit the viewer box (max-width/max-height, object-fit) - no text/code box rendered below the image for image pastes - link pill positioning cleaned up on the image view - /raw for attachment pastes redirects to the file itself - view details size now uses the attachment blob size, not empty text len --- internal/api/server.go | 8 ++++++++ internal/web/static/app.css | 10 +++++++++- internal/web/templates/paste.html | 6 ++++-- internal/web/web.go | 14 +++++++++++--- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/internal/api/server.go b/internal/api/server.go index dfe1ac4..84618c0 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -451,6 +451,14 @@ func (a *apiServer) handleRaw(w http.ResponseWriter, r *http.Request) { http.Error(w, "not found", 404) return } + // #221: an attachment paste has no text content; raw view must serve the + // file itself, not empty text. Redirect to the /f/ serving route, which + // applies the same sniffed-mime + disposition safety rules. + if att, err := a.store.GetAttachmentForPaste(row.ID); err == nil && att != nil { + a.store.IncrementViews(row.ID, "", 0) // raw views always count (#49/#95) + http.Redirect(w, r, "/f/"+att.ID+"/"+att.Filename, http.StatusFound) + return + } // #34: content_type is attacker-controlled via the create API. Serving it // verbatim let a paste be stored with text/html (or image/svg+xml) and // render as active content on this origin when fetched from /raw — diff --git a/internal/web/static/app.css b/internal/web/static/app.css index c96034b..b2fc912 100644 --- a/internal/web/static/app.css +++ b/internal/web/static/app.css @@ -818,7 +818,15 @@ button[type="submit"]:focus-visible, } .attachment-chip:hover { border-color: var(--accent); } .attachment-chip .attachment-size { color: var(--muted-fg); font-size: 19px; } -.attachment-preview img { max-width: 480px; max-height: 360px; border-radius: var(--radius); border: 1px solid var(--border); } +/* #221: image pastes scale to fit the viewer box (no text box below), and + the link pill sits above the image without drifting out of place. */ +.attachment-bar.is-image { align-items: flex-start; } +.attachment-bar.is-image .attachment-chip { max-width: 100%; } +.attachment-preview { max-width: 100%; } +.attachment-preview img { + display: block; max-width: 100%; max-height: 70vh; width: auto; height: auto; + object-fit: contain; border-radius: var(--radius); border: 1px solid var(--border); +} /* #139: CSP-safe replacements for inline style attributes (style-src 'self') */ .hidden { display: none; } diff --git a/internal/web/templates/paste.html b/internal/web/templates/paste.html index b907ded..45a6980 100644 --- a/internal/web/templates/paste.html +++ b/internal/web/templates/paste.html @@ -38,20 +38,22 @@ {{end}} {{if .Attachment}}
-
+
{{.Attachment.Filename}} {{.Attachment.SizeHuman}} - {{if or (eq .Attachment.Mime "image/png") (eq .Attachment.Mime "image/jpeg") (eq .Attachment.Mime "image/gif") (eq .Attachment.Mime "image/webp")}} + {{if .IsImagePaste}}
{{.Attachment.Filename}}
{{end}}
{{end}} + {{if not .IsImagePaste}}
{{.Gutter}}
{{.ContentHTML}}
+ {{end}}
diff --git a/internal/web/web.go b/internal/web/web.go index 0f87e16..3a91577 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -245,28 +245,36 @@ func (h *Handlers) renderPaste(w http.ResponseWriter, row *store.PasteRow, justC if lang == "" { lang = "text" } - summary := fmt.Sprintf("%s · %s · %d views · %s", lang, humanSize(len(row.Content)), row.ViewCount, agoString(row.CreatedAt)) // #38: one optional file attachment per paste; nil when none. attachment, err := h.Store.GetAttachmentForPaste(row.ID) if err != nil { http.Error(w, "db error", 500) return } + // #221: for attachment pastes the stored text content is empty (the file + // replaced it), so size must come from the attachment blob, not + // len(row.Content), or the view details show a wrong size. + sizeBytes := len(row.Content) + if attachment != nil { + sizeBytes = int(attachment.Size) + } + summary := fmt.Sprintf("%s · %s · %d views · %s", lang, humanSize(sizeBytes), row.ViewCount, agoString(row.CreatedAt)) data := map[string]any{ "Page": "paste", "ID": row.ID, "Title": row.Title.String, "Language": row.Language.String, "StatsSummary": summary, - "SizeHuman": humanSize(len(row.Content)), + "SizeHuman": humanSize(sizeBytes), "HasPassword": row.PasswordHash.Valid, "BurnAfterRead": row.BurnAfterRead, "CustomSlug": row.CustomSlug.String, + "IsImagePaste": attachment != nil && strings.HasPrefix(attachment.Mime, "image/"), "ContentHTML": template.HTML(langpkg.HighlightCode(row.Content, row.Language.String)), // safe: HighlightCode escapes all non-span text "ContentAttr": row.Content, "Gutter": strings.TrimSuffix(gutter, "\n"), "LineCount": lines, - "SizeBytes": len(row.Content), + "SizeBytes": sizeBytes, "CreatedAgo": agoString(row.CreatedAt), "CreatedAtUnix": row.CreatedAt, "ViewCount": row.ViewCount, -- 2.54.0 From c3f466565de0064c00a53a0f70bdc2547c02f347 Mon Sep 17 00:00:00 2001 From: fen Date: Thu, 10 Sep 2026 14:25:16 -0500 Subject: [PATCH 2/2] Merge fix-221 rework: pill padding cleanup, keep redirect-based raw view and image-paste flag --- internal/web/static/app.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/web/static/app.css b/internal/web/static/app.css index b2fc912..6855aa9 100644 --- a/internal/web/static/app.css +++ b/internal/web/static/app.css @@ -809,7 +809,7 @@ button[type="submit"]:focus-visible, font-size: 22px; padding: 0 4px; border-radius: var(--radius-sm); } .file-chip .file-chip-remove:hover { color: var(--danger, #c0392b); } -.attachment-bar { display: flex; flex-direction: column; gap: 10px; } +.attachment-bar { display: flex; flex-direction: column; gap: 10px; padding: 12px; } .attachment-chip { display: inline-flex; align-items: center; gap: 12px; align-self: flex-start; border: 1px solid var(--border); border-radius: var(--radius); -- 2.54.0