Project

General

Profile

Actions

Defect #44358

open

Markdownized preview shows broken images: Pandoc emits <img src="media/..."> but media is never extracted

Added by Igor St 22 days ago. Updated 12 days ago.

Status:
Confirmed
Priority:
Normal
Assignee:
-
Category:
Attachments
Target version:
-
Resolution:
Affected version:

Description

The Markdown-based preview for Office attachments introduced in 7.0 (feature #8959) renders broken image icons for documents that contain images. For documents that consist mostly of images, the entire preview is a set of broken icons.

I checked for duplicates first: #8959 is the closed feature request itself, and #44029 and #44225 are related but different. I found no existing report about media handling in markdownized previews.

Steps to reproduce

  1. Install Pandoc so that markdownized previews are enabled (verified with 3.10.2)
  2. Create a .docx containing one or more images
  3. Attach it to an issue and open the attachment preview

Actual behaviour

Every image is rendered as a broken image icon, and the web server access log shows:

GET /attachments/media/image1.png  ->  404
GET /attachments/media/image2.png  ->  404

Expected behaviour

The preview should not reference resources that were never produced. Either the media is extracted and served, or the image elements are removed, ideally replaced by a short note such as "N image(s) are not shown in this preview", so that an image-only document does not look like a rendering failure.

Cause

Redmine::Markdownizer#convert builds a fixed command line in lib/redmine/markdownizer.rb:49:

args = [COMMAND, source, "-t", "gfm"]

There is no --extract-media, so the image files are never written anywhere. Pandoc does not drop the images, though: for images carrying size attributes it emits raw HTML with a relative path, for example:

<img src="media/image1.png" style="width:10.68557in;height:6.01042in" />

app/views/attachments/markdownized.html.erb renders that content through common/markup with markup_text_formatting = 'common_mark'. The CommonMark formatter allows raw HTML (unsafe: true) and then sanitizes it; the sanitizer strips dangerous attributes but keeps the img element and its src. The browser resolves the relative path against the attachment URL, which produces the 404s above.

Measured example

A 504 KB .docx containing two images produced a 147-byte preview consisting of exactly two img tags. The 100 KB output cap (markdownized_preview_max_output_size) is not involved.

No stack trace available

There is nothing to attach: no exception is raised and production.log stays completely silent. Pandoc exits 0, the preview is generated and cached successfully, and the failure is visible only in the browser and as 404s in the web server access log. That silence is part of the problem, because an administrator gets no server-side signal that previews are broken.

Not a security issue

Verified on this installation: passing <img src="media/x.png" onerror="alert(1)" /><script>alert(2)</script> through Redmine::WikiFormatting.to_html('common_mark', ...) returns <img src="media/x.png"> with the onerror attribute removed and the script tag escaped. The sanitizer works as intended; this report is only about referencing files that do not exist.

Not an installation problem

dpkg -V reports no modified files, only one pandoc binary is present, and a control run of the same document with --extract-media extracts image1.png and image2.png and rewrites src to their absolute paths. Pandoc behaves exactly as documented; the missing flag is on the Redmine side.

Possible fixes

  • Option A: run Pandoc with --extract-media into a per-attachment subdirectory of Attachment.markdownized_previews_storage_path, serve those files through AttachmentsController under the same permission checks as the attachment itself, and rewrite the src paths. New settings could bound the cost, for example markdownized_preview_max_media_count and markdownized_preview_max_media_total_size. Note that serving the extracted media as plain static files would bypass Redmine permission checks and expose attachments of private issues, so it has to go through the controller.
  • Option B, minimal: post-process the Pandoc output to drop image elements and render a short note about the omitted images.

Environment

Redmine version                7.0.0.stable.24900
Ruby version                   3.2.0-p0 (2022-12-25) [x86_64-linux]
Rails version                  8.1.3.1
Environment                    production
Database adapter               PostgreSQL
Mailer queue                   ActiveJob::QueueAdapters::AsyncAdapter
Mailer delivery                smtp
Redmine theme                  Default
SCM: Subversion 1.14.1, Git 2.34.1, Filesystem

Source: svn branches/7.0-stable, working copy r24907, last changed r24900. PostgreSQL 14.23, Pandoc 3.10.2, Passenger 6.0.24 with Apache, Ubuntu 22.04.


Files

Actions #1

Updated by Go MAEDA 22 days ago

  • Status changed from New to Confirmed
Actions #2

Updated by Florian Walchshofer 15 days ago

I can confirm that the issue affects DOCX, PPTX and ODT files.

For comparison, XLSX files did not generate image references, so no broken images were displayed.

Extension HTTP request Generated markdown
.docx Started GET "/attachments/media/image1.png" <img src="media/image1.png" style="width:6.69306in;height:4.92708in" />
.pptx Started GET "/attachments/ppt/media/image1.png" ![](/ppt/media/image1.png)
.odt Started GET "/attachments/Pictures/1000000000000514000003BD9FECFAD7.png" <img src="Pictures/1000000000000514000003BD9FECFAD7.png"
style="width:17cm;height:12.515cm" />

However, the root cause seems to be the same: the preview contains references to media files that are never extracted and therefore cannot be served.

Actions #3

Updated by Florian Walchshofer 13 days ago

I attached a patch for this issue.

The fix stores previews and extracted media in an attachment-specific directory and serves media through a dedicated endpoint.

Cache structure

The markdownized preview cache structure was changed from a single cache file based on digest_filesize.md to an attachment-specific directory based on the attachment id.
This change is required to support Pandoc's --extract-media option.

Previously, previews were stored as:

markdownized_previews/
└── <digest>_<filesize>.md

With image extraction enabled, the structure now becomes:

markdownized_previews/
└── 4/
    ├── preview.md
    └── media/
        ├── image1.png
        ├── image2.png
        └── image3.png

Pandoc is executed from the derived_cache directory and uses:

--extract-media=markdownized_previews/<attachment_id>/

and

chdir: basedir

This causes Pandoc to generate image references such as:

markdownized_previews/4/media/image1.png

Using an attachment-specific directory allows these generated paths to be mapped directly to the new route:

GET /attachments/markdownized_previews/4/media/image1.png

Using the previous digest_filesize cache file naming scheme would not provide a suitable target directory for extracted media files and would make it difficult to expose them through predictable URLs.

Another option would have been to post-process and rewrite the generated markdown output to replace image paths after the Pandoc conversion. However, this approach would rely on assumptions about Pandoc's generated output and media path conventions.

By using --extract-media together with an attachment-specific directory, the implementation can rely on Pandoc's generated references directly without modifying the markdown afterwards. This is more robust across different document formats (DOCX, ODT, PPTX), supports arbitrary Pandoc-generated media paths, and is less sensitive to future changes in Pandoc's output.

The cleanup logic was also updated to remove the complete preview directory, including any extracted media files, when attachments are removed or cached previews are cleared.

Configuration

Regarding the suggested markdownized_preview_max_media_count and markdownized_preview_max_media_total_size settings, I think the existing limits already provide sufficient protection.

markdownized_preview_max_source_size already limits the size of documents processed by Pandoc, including all embedded images. Since extracted media originates from the source document itself, its total size is naturally constrained by the configured source document size limit.

This patch

  • extracts embedded images using --extract-media
  • stores previews and extracted media in an attachment-specific directory
    • support arbitrary Pandoc-generated media paths, including structures such as Pictures/... (ODT), ppt/media/... (PPTX) and media/... (DOCX)
  • adds a controller action and route for serving extracted media files
  • applies the same permission checks as attachment downloads and thumbnails
  • updates preview cleanup to remove extracted media files together with the generated preview
    • DOCX previews containing embedded images
    • ODT previews containing embedded images
    • Media file delivery through the new endpoint
    • Access control checks (authorized, unauthorized and forbidden access)
    • Missing files and invalid paths

The markdownized preview now correctly displays embedded media extracted by Pandoc while preserving Redmine's existing access control rules.

Actions #4

Updated by Florian Walchshofer 12 days ago

I attached an additional patch 0003 that implements a configurable placeholder mode for embedded images.

The patch introduces a new configuration setting:

markdownized_preview_images: render
Supported values:
  • render (default)
    • extracts embedded images using Pandoc's --extract-media option
    • renders images in the generated preview
  • placeholder
    • replaces embedded images with placeholders using a Pandoc Lua filter
    • preserves image positions in the document flow
    • does not extract media files
    • displays the image's alternative text when available, otherwise a generated placeholder such as 1.png

The implementation relies on a Pandoc Lua filter and does not perform post-processing of the generated markdown output.

This option provides a lightweight alternative for installations that prefer compact previews and reduced storage usage.
It may also be useful when a quick text-focused preview is preferred over rendering embedded images.

Actions

Also available in: Atom PDF