0f8d5b555a
Per Codex: a constrained Markdown-ish composer rather than contenteditable. * goodnews/markup.render_reply_html — escapes everything first, then introduces only a tiny whitelist (**bold**, - bullets, #/##/### headings, paragraphs, line breaks). No links, attributes, inline styles, or raw HTML passthrough. * feedback_replies.message_html column (+ live migration); replies store both the Markdown text and the rendered HTML. * email_send.send_feedback_reply now sends multipart text/plain + text/html (the sanitized render, wrapped in a trusted email template). * Frontend: textarea + a small toolbar (Bold / • List / H) that inserts Markdown; the reply thread renders the server-sanitized HTML. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
24 lines
818 B
Python
24 lines
818 B
Python
from goodnews.markup import render_reply_html as r
|
|
|
|
|
|
def test_escapes_before_formatting():
|
|
out = r("<script>alert(1)</script> and **bold**")
|
|
assert "<script>" not in out and "<script>" in out
|
|
assert "<strong>bold</strong>" in out
|
|
|
|
|
|
def test_bullets_and_heading_and_paragraphs():
|
|
assert r("- one\n- two") == "<ul><li>one</li><li>two</li></ul>"
|
|
assert r("## Update") == "<h4>Update</h4>"
|
|
assert r("a\nb\n\nc") == "<p>a<br>b</p><p>c</p>"
|
|
|
|
|
|
def test_no_links_or_attrs_pass_through():
|
|
out = r('[click](http://x) <a href="x" onclick="y">hi</a>')
|
|
assert "<a" not in out # no anchor tag produced or passed through (escaped to <a)
|
|
assert "[click]" in out # markdown links unsupported → left as literal text
|
|
|
|
|
|
def test_empty():
|
|
assert r(" ") == "" and r(None) == ""
|