Skip to content

FIX: Escape ] in URL label to prevent early link termination#40

Merged
gschlager merged 1 commit into
mainfrom
fix/escape-bracket-in-url-label
May 12, 2026
Merged

FIX: Escape ] in URL label to prevent early link termination#40
gschlager merged 1 commit into
mainfrom
fix/escape-bracket-in-url-label

Conversation

@gschlager
Copy link
Copy Markdown
Member

@gschlager gschlager commented May 12, 2026

Summary

  • MarkdownEscaper skips escaping ] in prose (harmless there, and a deliberate perf simplification), but ] is structural inside a Markdown link label
  • UrlTag and EmailTag both wrap already-escaped children in [label](url), so a link with text like [ABC-123] some title was rendered as [\[ABC-123] some title](…) — the unescaped ] closed the label early and Discourse rendered it as literal text
  • Fix escapes ] at the bracket-emitting site in each tag (not in the shared escaper) so prose stays untouched
  • html_mode path (which emits <a>…</a>) is unaffected; ] is not structural in HTML

Test plan

  • bundle exec rspec spec/unit/markbridge/renderers/discourse/tags/url_tag_spec.rb — 17 examples, 0 failures
  • bundle exec rspec spec/unit/markbridge/renderers/discourse/tags/email_tag_spec.rb — 7 examples, 0 failures
  • bundle exec rspec — full suite: 3106 examples, 0 failures
  • bin/lint — no offenses

MarkdownEscaper deliberately skips escaping ] in normal prose where
it's harmless, but ] is structural inside a Markdown link label.
UrlTag and EmailTag both wrap already-escaped children in
[label](url), so an input like "[ABC-123] some title" was rendered as
"[\[ABC-123] some title](…)" — the unescaped ] closed the label
early and Discourse rendered the result as literal text. Escape ] at
each bracket-emitting site rather than in the shared escaper so prose
output stays untouched.
@gschlager gschlager force-pushed the fix/escape-bracket-in-url-label branch from 841d99b to 2efccab Compare May 12, 2026 13:36
@gschlager gschlager merged commit d3eeb1a into main May 12, 2026
7 checks passed
@gschlager gschlager deleted the fix/escape-bracket-in-url-label branch May 12, 2026 13:36
gschlager added a commit that referenced this pull request May 12, 2026
The previous fix (#40) escaped ] in UrlTag/EmailTag with an
unconditional `text.gsub("]") { "\\]" }`. That regressed nested
markdown that legitimately produces unescaped ] — for example
an image inside a link:

    <a><img src="…"/></a>

renders the image as `![](src)`, and the tag-level gsub turned the
label into `[![\](src)](href)` — broken.

The escape rule is contextual to the text content, not to how it
gets wrapped. Move the decision to where escaping already happens:

- MarkdownEscaper#escape grows an `in_link_label:` kwarg. When set
  and the result contains ], it post-processes the result to escape
  every ]. The non-label hot path is unchanged (one boolean check).
- Renderer#render_text detects an Url/Email ancestor via
  has_parent? and passes the flag through. Only plain Text nodes
  flow through this — nested tag output (ImageTag, UploadTag, etc.)
  doesn't touch render_text, so its structural ] is preserved.
- UrlTag and EmailTag go back to a plain "[#{text}](#{href})"
  wrapper.

Performance: benchmarked the hot path (non-label) against main at
1.79–1.84M i/s for plain text and ~245k i/s for text with specials
— no measurable regression. The label path's no-`]` fast-return
preserves the input string instance (locked in by a spec).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant