diff --git a/app/assets/javascripts/posts.js b/app/assets/javascripts/posts.js index 25c0fe65a..955587c4a 100644 --- a/app/assets/javascripts/posts.js +++ b/app/assets/javascripts/posts.js @@ -6,6 +6,19 @@ const ALLOWED_ATTR = ['id', 'class', 'href', 'title', 'src', 'height', 'width', 'start', 'dir']; $(() => { + DOMPurify.addHook("uponSanitizeAttribute", (node, event) => { + const rowspan = node.getAttribute("rowspan"); + const colspan = node.getAttribute("colspan"); + + if (rowspan && Number.isNaN(+rowspan)) { + event.keepAttr = false; + } + + if (colspan && Number.isNaN(+colspan)) { + event.keepAttr = false; + } + }); + const $uploadForm = $('.js-upload-form'); const stringInsert = (str, idx, insert) => str.slice(0, idx) + insert + str.slice(idx); @@ -149,6 +162,27 @@ $(() => { ALLOWED_TAGS, ALLOWED_ATTR }); + + const removedElements = [...new Set(DOMPurify.removed + .filter(entry => entry.element && !(entry.element instanceof HTMLBodyElement)) + .map(entry => entry.element.localName))]; + + const removedAttributes = [...new Set(DOMPurify.removed + .filter(entry => entry.attribute) + .map(entry => [ + entry.attribute.name + (entry.attribute.value ? `='${entry.attribute.value}'` : ''), + entry.from.localName + ]))] + + $tgt.parents('form') + .find('.rejected-elements') + .toggleClass('hide', removedElements.length === 0 && removedAttributes.length === 0) + .find('ul') + .empty() + .append( + removedElements.map(name => $(`
  • <${name}>
  • `)), + removedAttributes.map(([attr, elName]) => $(`
  • ${attr} (in <${elName}>)
  • `))); + $tgt.parents('.form-group').siblings('.post-preview').html(html); $tgt.parents('form').find('.js-post-html[name="__html"]').val(html + ''); }, 0); diff --git a/app/views/posts/_form.html.erb b/app/views/posts/_form.html.erb index 47a8eaf77..92a345698 100644 --- a/app/views/posts/_form.html.erb +++ b/app/views/posts/_form.html.erb @@ -57,6 +57,14 @@ <%= render 'shared/body_field', f: f, field_name: :body_markdown, field_label: t('posts.body_label'), post: post, min_length: min_body_length(category), max_length: max_body_length(category) %> +
    +

    Unsupported HTML detected

    +

    The following HTML tags and attributes are unsupported and will be removed from the final post:

    + +

    For a list of allowed HTML, see this help article. + If you meant to display the tags as code in the post, please enclose them in a code block.

    +
    <% unless post_type.has_parent? %>