mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-02 04:42:19 +08:00
Filenames containing markdown formatting characters (`_`, `*`, `~`, `` `
``, `[`, `]`, `|`) would break upload markup when cooked. For example,
uploading `_test_file_.txt` generated:
[_test_file_.txt|attachment](upload://...)
The underscores triggered emphasis parsing inside the link text, which
both rendered the filename incorrectly (with italics) and prevented the
`|attachment` marker from being recognized — losing the
`class="attachment"` on the resulting `<a>` tag.
**Markdown generation (defense in depth)**
Add `escapeMarkdownCharacters` (JS) and `UploadMarkdown.escape_markdown`
(Ruby) to backslash-escape all inline formatting characters in filenames
before embedding them in markdown link text. Applied in:
- `UploadMarkdown` — image, attachment, and playable media methods
- `uploads.js` — `attachmentMarkdown` and `markdownNameFromFileName`
- `inline_uploads.rb` — HTML anchor conversion and hotlinked image URLs
- `to-markdown.js` — HTML-to-markdown attachment link reconstruction
- `sanitizeAlt` in `markdown-image-builder.js` — image alt text
**Parser resilience (belt and suspenders)**
The markdown-it `renderAttachment` renderer and ProseMirror's link
parser both assumed `tokens[idx+1]` was a single text token containing
the full link text. When emphasis/bold/strikethrough/code was parsed
inside the link text, the token sequence included formatting tokens and
the `|attachment` marker was lost. Both now scan forward through all
tokens between `link_open` and `link_close` to find the marker.
The image renderer (`renderImageOrPlayableMedia`) split alt text on `|`
assuming the first segment was always the alt and everything after was
structured suffixes (dimensions, video/audio, data attributes). A pipe
in the filename would produce extra segments that confused the dimension
parser. It now scans from the right, consuming known suffixes, and
treats everything remaining as alt text.
https://meta.discourse.org/t/400079
59 lines
2.4 KiB
Ruby
59 lines
2.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe UploadMarkdown do
|
|
it "generates markdown for each different upload type (attachment, image, video, audio)" do
|
|
SiteSetting.authorized_extensions = "mp4|mp3|pdf|jpg|mmmppp444"
|
|
video = Fabricate(:upload, original_filename: "test_video.mp4", extension: "mp4")
|
|
audio = Fabricate(:upload, original_filename: "test_audio.mp3", extension: "mp3")
|
|
attachment = Fabricate(:upload, original_filename: "test_file.pdf", extension: "pdf")
|
|
image =
|
|
Fabricate(
|
|
:upload,
|
|
width: 100,
|
|
height: 200,
|
|
original_filename: "test_img.jpg",
|
|
extension: "jpg",
|
|
)
|
|
|
|
expect(UploadMarkdown.new(video).to_markdown).to eq(<<~MD.chomp)
|
|

|
|
MD
|
|
expect(UploadMarkdown.new(audio).to_markdown).to eq(<<~MD.chomp)
|
|

|
|
MD
|
|
expect(UploadMarkdown.new(attachment).to_markdown).to eq(<<~MD.chomp)
|
|
[test\\_file.pdf|attachment](#{attachment.short_url}) (#{attachment.human_filesize})
|
|
MD
|
|
expect(UploadMarkdown.new(image).to_markdown).to eq(<<~MD.chomp)
|
|

|
|
MD
|
|
|
|
unknown = Fabricate(:upload, original_filename: "test_video.mmmppp444", extension: "mmmppp444")
|
|
expect(UploadMarkdown.new(unknown).playable_media_markdown).to eq(<<~MD.chomp)
|
|
[test\\_video.mmmppp444|attachment](#{unknown.short_url}) (#{unknown.human_filesize})
|
|
MD
|
|
end
|
|
|
|
it "escapes markdown characters in attachment filenames" do
|
|
SiteSetting.authorized_extensions = "txt"
|
|
|
|
{
|
|
"_test_file_.txt" => "<em>",
|
|
"*test*.txt" => "<em>",
|
|
"**bold**.txt" => "<strong>",
|
|
"~~strike~~.txt" => "<s>",
|
|
"`code`.txt" => "<code>",
|
|
}.each do |filename, bad_tag|
|
|
upload = Fabricate(:upload, original_filename: filename, extension: "txt")
|
|
markdown = UploadMarkdown.new(upload).attachment_markdown
|
|
cooked = PrettyText.cook(markdown)
|
|
|
|
expect(cooked).to include('class="attachment"'),
|
|
"expected attachment class for filename: #{filename}\ncooked: #{cooked}"
|
|
expect(cooked).not_to include(bad_tag),
|
|
"unexpected #{bad_tag} in cooked output for filename: #{filename}\ncooked: #{cooked}"
|
|
expect(cooked).to include(filename.gsub("\\", "")),
|
|
"expected display name for filename: #{filename}\ncooked: #{cooked}"
|
|
end
|
|
end
|
|
end
|