|
| 1 | +"""Sanitized markup renderers for MarkupField content. |
| 2 | +
|
| 3 | +django-markupfield marks the rendered markdown/RST output safe and templates |
| 4 | +print it with ``|safe``. The bundled renderers preserve link schemes such as |
| 5 | +``javascript:`` in hrefs, and the site sends no Content-Security-Policy, so an |
| 6 | +unsafe scheme in a markup link would execute in the browser of anyone viewing |
| 7 | +the page. |
| 8 | +
|
| 9 | +Each default renderer is wrapped so its HTML passes through ``nh3.clean`` with |
| 10 | +an http/https/mailto URL-scheme allowlist (plus a tag/attribute allowlist) |
| 11 | +before it is marked safe. Wiring this through ``MARKUP_FIELD_TYPES`` covers |
| 12 | +every MarkupField (nominations, jobs, success stories, comments) at once. |
| 13 | +""" |
| 14 | + |
| 15 | +import nh3 |
| 16 | +from markupfield.markup import DEFAULT_MARKUP_TYPES |
| 17 | + |
| 18 | +ALLOWED_TAGS = { |
| 19 | + "a", |
| 20 | + "abbr", |
| 21 | + "b", |
| 22 | + "blockquote", |
| 23 | + "br", |
| 24 | + "caption", |
| 25 | + "code", |
| 26 | + "col", |
| 27 | + "colgroup", |
| 28 | + "dd", |
| 29 | + "del", |
| 30 | + "div", |
| 31 | + "dl", |
| 32 | + "dt", |
| 33 | + "em", |
| 34 | + "figcaption", |
| 35 | + "figure", |
| 36 | + "h1", |
| 37 | + "h2", |
| 38 | + "h3", |
| 39 | + "h4", |
| 40 | + "h5", |
| 41 | + "h6", |
| 42 | + "hr", |
| 43 | + "i", |
| 44 | + "img", |
| 45 | + "ins", |
| 46 | + "kbd", |
| 47 | + "li", |
| 48 | + "ol", |
| 49 | + "p", |
| 50 | + "pre", |
| 51 | + "s", |
| 52 | + "span", |
| 53 | + "strong", |
| 54 | + "sub", |
| 55 | + "sup", |
| 56 | + "table", |
| 57 | + "tbody", |
| 58 | + "td", |
| 59 | + "tfoot", |
| 60 | + "th", |
| 61 | + "thead", |
| 62 | + "tr", |
| 63 | + "ul", |
| 64 | +} |
| 65 | + |
| 66 | +ALLOWED_ATTRIBUTES = { |
| 67 | + "*": {"class", "id", "title"}, |
| 68 | + "a": {"href"}, |
| 69 | + "img": {"src", "alt", "width", "height"}, |
| 70 | + "td": {"align", "colspan", "rowspan"}, |
| 71 | + "th": {"align", "colspan", "rowspan", "scope"}, |
| 72 | +} |
| 73 | + |
| 74 | +ALLOWED_URL_SCHEMES = {"http", "https", "mailto"} |
| 75 | + |
| 76 | + |
| 77 | +def sanitize(html): |
| 78 | + """Drop links and attributes whose scheme/name is not allowlisted.""" |
| 79 | + return nh3.clean( |
| 80 | + html, |
| 81 | + tags=ALLOWED_TAGS, |
| 82 | + attributes=ALLOWED_ATTRIBUTES, |
| 83 | + url_schemes=ALLOWED_URL_SCHEMES, |
| 84 | + ) |
| 85 | + |
| 86 | + |
| 87 | +def _sanitizing(render): |
| 88 | + def sanitizing_render(markup): |
| 89 | + return sanitize(render(markup)) |
| 90 | + |
| 91 | + return sanitizing_render |
| 92 | + |
| 93 | + |
| 94 | +MARKUP_FIELD_TYPES = [(name, _sanitizing(render), *rest) for name, render, *rest in DEFAULT_MARKUP_TYPES] |
0 commit comments