2019-07-03 08:53:00 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Banzai
|
|
|
|
module Filter
|
|
|
|
# Sanitize HTML produced by AsciiDoc/Asciidoctor.
|
|
|
|
#
|
|
|
|
# Extends Banzai::Filter::BaseSanitizationFilter with specific rules.
|
|
|
|
class AsciiDocSanitizationFilter < Banzai::Filter::BaseSanitizationFilter
|
2020-12-07 21:10:08 +00:00
|
|
|
# Anchor link prefixed by "user-content-" pattern
|
|
|
|
PREFIXED_ID_PATTERN = /\A#{Gitlab::Asciidoc::DEFAULT_ADOC_ATTRS['idprefix']}(:?[[:alnum:]]|-|_)+\z/.freeze
|
2019-07-10 08:30:10 +00:00
|
|
|
SECTION_HEADINGS = %w(h2 h3 h4 h5 h6).freeze
|
|
|
|
|
|
|
|
# Footnote link patterns
|
|
|
|
FOOTNOTE_LINK_ID_PATTERNS = {
|
|
|
|
a: /\A_footnoteref_\d+\z/,
|
|
|
|
div: /\A_footnotedef_\d+\z/
|
|
|
|
}.freeze
|
2019-07-10 08:30:10 +00:00
|
|
|
|
2019-07-03 08:53:00 +00:00
|
|
|
# Classes used by Asciidoctor to style components
|
|
|
|
ADMONITION_CLASSES = %w(fa icon-note icon-tip icon-warning icon-caution icon-important).freeze
|
2020-05-29 03:08:11 +00:00
|
|
|
ALIGNMENT_BUILTINS_CLASSES = %w(text-center text-left text-right text-justify).freeze
|
2019-07-03 08:53:00 +00:00
|
|
|
CALLOUT_CLASSES = ['conum'].freeze
|
|
|
|
CHECKLIST_CLASSES = %w(fa fa-check-square-o fa-square-o).freeze
|
|
|
|
LIST_CLASSES = %w(checklist none no-bullet unnumbered unstyled).freeze
|
|
|
|
|
2019-10-15 18:06:01 +00:00
|
|
|
TABLE_FRAME_CLASSES = %w(frame-all frame-topbot frame-sides frame-ends frame-none).freeze
|
|
|
|
TABLE_GRID_CLASSES = %w(grid-all grid-rows grid-cols grid-none).freeze
|
|
|
|
TABLE_STRIPES_CLASSES = %w(stripes-all stripes-odd stripes-even stripes-hover stripes-none).freeze
|
|
|
|
|
2021-01-04 21:10:19 +00:00
|
|
|
ELEMENT_CLASSES_ALLOWLIST = {
|
2019-07-03 08:53:00 +00:00
|
|
|
span: %w(big small underline overline line-through).freeze,
|
2020-05-29 03:08:11 +00:00
|
|
|
div: ALIGNMENT_BUILTINS_CLASSES + ['admonitionblock'].freeze,
|
2019-07-03 08:53:00 +00:00
|
|
|
td: ['icon'].freeze,
|
|
|
|
i: ADMONITION_CLASSES + CALLOUT_CLASSES + CHECKLIST_CLASSES,
|
|
|
|
ul: LIST_CLASSES,
|
2019-07-10 08:30:10 +00:00
|
|
|
ol: LIST_CLASSES,
|
2019-10-15 18:06:01 +00:00
|
|
|
a: ['anchor'].freeze,
|
|
|
|
table: TABLE_FRAME_CLASSES + TABLE_GRID_CLASSES + TABLE_STRIPES_CLASSES
|
2019-07-03 08:53:00 +00:00
|
|
|
}.freeze
|
|
|
|
|
2021-01-04 21:10:19 +00:00
|
|
|
def customize_allowlist(allowlist)
|
2019-07-03 08:53:00 +00:00
|
|
|
# Allow marks
|
2021-01-04 21:10:19 +00:00
|
|
|
allowlist[:elements].push('mark')
|
2019-07-03 08:53:00 +00:00
|
|
|
|
2019-07-10 08:30:10 +00:00
|
|
|
# Allow any classes in `span`, `i`, `div`, `td`, `ul`, `ol` and `a` elements
|
2019-07-03 08:53:00 +00:00
|
|
|
# but then remove any unknown classes
|
2021-01-04 21:10:19 +00:00
|
|
|
allowlist[:attributes]['span'] = %w(class)
|
|
|
|
allowlist[:attributes]['div'].push('class')
|
|
|
|
allowlist[:attributes]['td'] = %w(class)
|
|
|
|
allowlist[:attributes]['i'] = %w(class)
|
|
|
|
allowlist[:attributes]['ul'] = %w(class)
|
|
|
|
allowlist[:attributes]['ol'] = %w(class)
|
|
|
|
allowlist[:attributes]['a'].push('class')
|
|
|
|
allowlist[:attributes]['table'] = %w(class)
|
|
|
|
allowlist[:transformers].push(self.class.remove_element_classes)
|
2019-07-03 08:53:00 +00:00
|
|
|
|
2020-12-07 21:10:08 +00:00
|
|
|
# Allow `id` in anchor and footnote elements
|
2021-01-04 21:10:19 +00:00
|
|
|
allowlist[:attributes]['a'].push('id')
|
|
|
|
allowlist[:attributes]['div'].push('id')
|
2020-12-07 21:10:08 +00:00
|
|
|
|
2019-07-10 08:30:10 +00:00
|
|
|
# Allow `id` in heading elements for section anchors
|
2019-07-10 08:30:10 +00:00
|
|
|
SECTION_HEADINGS.each do |header|
|
2021-01-04 21:10:19 +00:00
|
|
|
allowlist[:attributes][header] = %w(id)
|
2019-07-10 08:30:10 +00:00
|
|
|
end
|
|
|
|
|
2020-12-07 21:10:08 +00:00
|
|
|
# Remove ids that are not explicitly allowed
|
2021-01-04 21:10:19 +00:00
|
|
|
allowlist[:transformers].push(self.class.remove_disallowed_ids)
|
2019-07-10 08:30:10 +00:00
|
|
|
|
2021-01-04 21:10:19 +00:00
|
|
|
allowlist
|
2019-07-03 08:53:00 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
class << self
|
2020-12-07 21:10:08 +00:00
|
|
|
def remove_disallowed_ids
|
2019-07-10 08:30:10 +00:00
|
|
|
lambda do |env|
|
|
|
|
node = env[:node]
|
|
|
|
|
2020-12-07 21:10:08 +00:00
|
|
|
return unless node.name == 'a' || node.name == 'div' || SECTION_HEADINGS.any?(node.name)
|
2019-07-10 08:30:10 +00:00
|
|
|
return unless node.has_attribute?('id')
|
|
|
|
|
2020-12-07 21:10:08 +00:00
|
|
|
return if node['id'] =~ PREFIXED_ID_PATTERN
|
2019-07-10 08:30:10 +00:00
|
|
|
|
2020-12-07 21:10:08 +00:00
|
|
|
if (pattern = FOOTNOTE_LINK_ID_PATTERNS[node.name.to_sym])
|
|
|
|
return if node['id'] =~ pattern
|
|
|
|
end
|
2019-07-10 08:30:10 +00:00
|
|
|
|
|
|
|
node.remove_attribute('id')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-03 08:53:00 +00:00
|
|
|
def remove_element_classes
|
|
|
|
lambda do |env|
|
|
|
|
node = env[:node]
|
|
|
|
|
2021-01-04 21:10:19 +00:00
|
|
|
return unless (classes_allowlist = ELEMENT_CLASSES_ALLOWLIST[node.name.to_sym])
|
2019-07-03 08:53:00 +00:00
|
|
|
return unless node.has_attribute?('class')
|
|
|
|
|
|
|
|
classes = node['class'].strip.split(' ')
|
2021-01-04 21:10:19 +00:00
|
|
|
allowed_classes = (classes & classes_allowlist)
|
2019-07-03 08:53:00 +00:00
|
|
|
if allowed_classes.empty?
|
|
|
|
node.remove_attribute('class')
|
|
|
|
else
|
|
|
|
node['class'] = allowed_classes.join(' ')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|