diff options
Diffstat (limited to 'lib/banzai/filter')
-rw-r--r-- | lib/banzai/filter/asset_proxy_filter.rb | 4 | ||||
-rw-r--r-- | lib/banzai/filter/custom_emoji_filter.rb | 62 | ||||
-rw-r--r-- | lib/banzai/filter/feature_flag_reference_filter.rb | 33 | ||||
-rw-r--r-- | lib/banzai/filter/markdown_post_escape_filter.rb | 40 | ||||
-rw-r--r-- | lib/banzai/filter/markdown_pre_escape_filter.rb | 43 | ||||
-rw-r--r-- | lib/banzai/filter/plantuml_filter.rb | 2 | ||||
-rw-r--r-- | lib/banzai/filter/truncate_source_filter.rb | 4 |
7 files changed, 185 insertions, 3 deletions
diff --git a/lib/banzai/filter/asset_proxy_filter.rb b/lib/banzai/filter/asset_proxy_filter.rb index 55dc426edaf..4c14ee7299b 100644 --- a/lib/banzai/filter/asset_proxy_filter.rb +++ b/lib/banzai/filter/asset_proxy_filter.rb @@ -59,7 +59,9 @@ module Banzai end def self.determine_allowlist(application_settings) - application_settings.asset_proxy_whitelist.presence || [Gitlab.config.gitlab.host] + application_settings.try(:asset_proxy_allowlist).presence || + application_settings.try(:asset_proxy_whitelist).presence || + [Gitlab.config.gitlab.host] end end end diff --git a/lib/banzai/filter/custom_emoji_filter.rb b/lib/banzai/filter/custom_emoji_filter.rb new file mode 100644 index 00000000000..1ee8f4e31e8 --- /dev/null +++ b/lib/banzai/filter/custom_emoji_filter.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Banzai + module Filter + class CustomEmojiFilter < HTML::Pipeline::Filter + IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set + + def call + return doc unless context[:project] + return doc unless Feature.enabled?(:custom_emoji, context[:project]) + + doc.search(".//text()").each do |node| + content = node.to_html + + next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS) + next unless content.include?(':') + next unless namespace && namespace.custom_emoji.any? + + html = custom_emoji_name_element_filter(content) + + node.replace(html) unless html == content + end + + doc + end + + def custom_emoji_pattern + @emoji_pattern ||= + /(?<=[^[:alnum:]:]|\n|^) + :(#{CustomEmoji::NAME_REGEXP}): + (?=[^[:alnum:]:]|$)/x + end + + def custom_emoji_name_element_filter(text) + text.gsub(custom_emoji_pattern) do |match| + name = Regexp.last_match[1] + custom_emoji = all_custom_emoji[name] + + if custom_emoji + Gitlab::Emoji.custom_emoji_tag(custom_emoji.name, custom_emoji.url) + else + match + end + end + end + + private + + def namespace + context[:project].namespace.root_ancestor + end + + def custom_emoji_candidates + doc.to_html.scan(/:(#{CustomEmoji::NAME_REGEXP}):/).flatten + end + + def all_custom_emoji + @all_custom_emoji ||= namespace.custom_emoji.by_name(custom_emoji_candidates).index_by(&:name) + end + end + end +end diff --git a/lib/banzai/filter/feature_flag_reference_filter.rb b/lib/banzai/filter/feature_flag_reference_filter.rb new file mode 100644 index 00000000000..c11576901ce --- /dev/null +++ b/lib/banzai/filter/feature_flag_reference_filter.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Banzai + module Filter + class FeatureFlagReferenceFilter < IssuableReferenceFilter + self.reference_type = :feature_flag + + def self.object_class + Operations::FeatureFlag + end + + def self.object_sym + :feature_flag + end + + def parent_records(parent, ids) + parent.operations_feature_flags.where(iid: ids.to_a) + end + + def url_for_object(feature_flag, project) + ::Gitlab::Routing.url_helpers.edit_project_feature_flag_url( + project, + feature_flag.iid, + only_path: context[:only_path] + ) + end + + def object_link_title(object, matches) + object.name + end + end + end +end diff --git a/lib/banzai/filter/markdown_post_escape_filter.rb b/lib/banzai/filter/markdown_post_escape_filter.rb new file mode 100644 index 00000000000..ad32e9afbf5 --- /dev/null +++ b/lib/banzai/filter/markdown_post_escape_filter.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Banzai + module Filter + class MarkdownPostEscapeFilter < HTML::Pipeline::Filter + LITERAL_KEYWORD = MarkdownPreEscapeFilter::LITERAL_KEYWORD + LITERAL_REGEX = %r{#{LITERAL_KEYWORD}-(.*?)-#{LITERAL_KEYWORD}}.freeze + NOT_LITERAL_REGEX = %r{#{LITERAL_KEYWORD}-((%5C|\\).+?)-#{LITERAL_KEYWORD}}.freeze + SPAN_REGEX = %r{<span>(.*?)</span>}.freeze + + def call + return doc unless result[:escaped_literals] + + # For any literals that actually didn't get escape processed + # (for example in code blocks), remove the special sequence. + html.gsub!(NOT_LITERAL_REGEX, '\1') + + # Replace any left over literal sequences with `span` so that our + # reference processing is short-circuited + html.gsub!(LITERAL_REGEX, '<span>\1</span>') + + # Since literals are converted in links, we need to remove any surrounding `span`. + # Note: this could have been done in the renderer, + # Banzai::Renderer::CommonMark::HTML. However, we eventually want to use + # the built-in compiled renderer, rather than the ruby version, for speed. + # So let's do this work here. + doc.css('a').each do |node| + node.attributes['href'].value = node.attributes['href'].value.gsub(SPAN_REGEX, '\1') if node.attributes['href'] + node.attributes['title'].value = node.attributes['title'].value.gsub(SPAN_REGEX, '\1') if node.attributes['title'] + end + + doc.css('code').each do |node| + node.attributes['lang'].value = node.attributes['lang'].value.gsub(SPAN_REGEX, '\1') if node.attributes['lang'] + end + + doc + end + end + end +end diff --git a/lib/banzai/filter/markdown_pre_escape_filter.rb b/lib/banzai/filter/markdown_pre_escape_filter.rb new file mode 100644 index 00000000000..9fd77c48659 --- /dev/null +++ b/lib/banzai/filter/markdown_pre_escape_filter.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Banzai + module Filter + # In order to allow a user to short-circuit our reference shortcuts + # (such as # or !), the user should be able to escape them, like \#. + # CommonMark supports this, however it removes all information about + # what was actually a literal. In order to short-circuit the reference, + # we must surround backslash escaped ASCII punctuation with a custom sequence. + # This way CommonMark will properly handle the backslash escaped chars + # but we will maintain knowledge (the sequence) that it was a literal. + # + # We need to surround the character, not just prefix it. It could + # get converted into an entity by CommonMark and we wouldn't know how many + # characters there are. The entire literal needs to be surrounded with + # a `span` tag, which short-circuits our reference processing. + # + # We can't use a custom HTML tag since we could be initially surrounding + # text in an href, and then CommonMark will not be able to parse links + # properly. So we use `cmliteral-` and `-cmliteral` + # + # https://spec.commonmark.org/0.29/#backslash-escapes + # + # This filter does the initial surrounding, and MarkdownPostEscapeFilter + # does the conversion into span tags. + class MarkdownPreEscapeFilter < HTML::Pipeline::TextFilter + ASCII_PUNCTUATION = %r{([\\][!"#$%&'()*+,-./:;<=>?@\[\\\]^_`{|}~])}.freeze + LITERAL_KEYWORD = 'cmliteral' + + def call + return @text unless Feature.enabled?(:honor_escaped_markdown, context[:group] || context[:project]&.group) + + @text.gsub(ASCII_PUNCTUATION) do |match| + # The majority of markdown does not have literals. If none + # are found, we can bypass the post filter + result[:escaped_literals] = true + + "#{LITERAL_KEYWORD}-#{match}-#{LITERAL_KEYWORD}" + end + end + end + end +end diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb index 1a75cd14b11..37d4126c1ba 100644 --- a/lib/banzai/filter/plantuml_filter.rb +++ b/lib/banzai/filter/plantuml_filter.rb @@ -9,7 +9,7 @@ module Banzai # class PlantumlFilter < HTML::Pipeline::Filter def call - return doc unless doc.at('pre > code[lang="plantuml"]') && settings.plantuml_enabled + return doc unless settings.plantuml_enabled? && doc.at('pre > code[lang="plantuml"]') plantuml_setup diff --git a/lib/banzai/filter/truncate_source_filter.rb b/lib/banzai/filter/truncate_source_filter.rb index c903b83d868..44f88b253d9 100644 --- a/lib/banzai/filter/truncate_source_filter.rb +++ b/lib/banzai/filter/truncate_source_filter.rb @@ -6,7 +6,9 @@ module Banzai def call return text unless context.key?(:limit) - text.truncate_bytes(context[:limit]) + # Use three dots instead of the ellipsis Unicode character because + # some clients show the raw Unicode value in the merge commit. + text.truncate_bytes(context[:limit], omission: '...') end end end |