diff options
Diffstat (limited to 'lib/banzai')
-rw-r--r-- | lib/banzai/filter/attributes_filter.rb | 51 | ||||
-rw-r--r-- | lib/banzai/filter/inline_observability_filter.rb | 30 | ||||
-rw-r--r-- | lib/banzai/filter/repository_link_filter.rb | 2 | ||||
-rw-r--r-- | lib/banzai/filter/sanitization_filter.rb | 2 | ||||
-rw-r--r-- | lib/banzai/filter/syntax_highlight_filter.rb | 4 | ||||
-rw-r--r-- | lib/banzai/filter/timeout_html_pipeline_filter.rb | 38 | ||||
-rw-r--r-- | lib/banzai/pipeline/ascii_doc_pipeline.rb | 2 | ||||
-rw-r--r-- | lib/banzai/pipeline/gfm_pipeline.rb | 4 | ||||
-rw-r--r-- | lib/banzai/pipeline/markup_pipeline.rb | 2 | ||||
-rw-r--r-- | lib/banzai/pipeline/wiki_pipeline.rb | 4 | ||||
-rw-r--r-- | lib/banzai/reference_parser/alert_parser.rb | 8 | ||||
-rw-r--r-- | lib/banzai/reference_parser/base_parser.rb | 8 |
12 files changed, 144 insertions, 11 deletions
diff --git a/lib/banzai/filter/attributes_filter.rb b/lib/banzai/filter/attributes_filter.rb new file mode 100644 index 00000000000..ab50b3d6858 --- /dev/null +++ b/lib/banzai/filter/attributes_filter.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Banzai + module Filter + # Looks for attributes that are specified for an element. Follows the basic syntax laid out + # in https://github.com/jgm/commonmark-hs/blob/master/commonmark-extensions/test/attributes.md + # For example, + # ![](http://example.com/image.jpg){width=50%} + # + # However we currently have the following limitations: + # - only support images + # - only support the `width` and `height` attributes + # - attributes can not span multiple lines + # - unsupported attributes are thrown away + class AttributesFilter < HTML::Pipeline::Filter + CSS = 'img' + XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze + + ATTRIBUTES_PATTERN = %r{\A(?<matched>\{(?<attributes>.{1,100})\})}.freeze + WIDTH_HEIGHT_REGEX = %r{\A(?<name>height|width)="?(?<size>[\w%]{1,10})"?\z}.freeze + VALID_SIZE_REGEX = %r{\A\d{1,4}(%|px)?\z}.freeze + + def call + doc.xpath(XPATH).each do |img| + sibling = img.next + next unless sibling && sibling.text? && sibling.content.first == '{' + + match = sibling.content.match(ATTRIBUTES_PATTERN) + next unless match && match[:attributes] + + match[:attributes].split(' ').each do |attribute| + next unless attribute.match?(WIDTH_HEIGHT_REGEX) + + attribute_match = attribute.match(WIDTH_HEIGHT_REGEX) + img[attribute_match[:name].to_sym] = attribute_match[:size] if valid_size?(attribute_match[:size]) + end + + sibling.content = sibling.content.sub(match[:matched], '') + end + + doc + end + + private + + def valid_size?(size) + size.match?(VALID_SIZE_REGEX) + end + end + end +end diff --git a/lib/banzai/filter/inline_observability_filter.rb b/lib/banzai/filter/inline_observability_filter.rb new file mode 100644 index 00000000000..27b89073a0e --- /dev/null +++ b/lib/banzai/filter/inline_observability_filter.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Banzai + module Filter + class InlineObservabilityFilter < ::Banzai::Filter::InlineEmbedsFilter + # Placeholder element for the frontend to use as an + # injection point for observability. + def create_element(url) + doc.document.create_element( + 'div', + class: 'js-render-observability', + 'data-frame-url': url + ) + end + + # Search params for selecting observability links. + def xpath_search + "descendant-or-self::a[starts-with(@href, '#{Gitlab::Observability.observability_url}')]" + end + + # Creates a new element based on the parameters + # obtained from the target link + def element_to_embed(node) + url = node['href'] + + create_element(url) + end + end + end +end diff --git a/lib/banzai/filter/repository_link_filter.rb b/lib/banzai/filter/repository_link_filter.rb index e95da735647..86beeae01b7 100644 --- a/lib/banzai/filter/repository_link_filter.rb +++ b/lib/banzai/filter/repository_link_filter.rb @@ -60,7 +60,7 @@ module Banzai def get_uri_types(paths) return {} if paths.empty? - uri_types = paths.to_h { |name| [name, nil] } + uri_types = paths.index_with { nil } get_blob_types(paths).each do |name, type| if type == :blob diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index fe189b1b0c9..de9b846efe9 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -10,6 +10,8 @@ module Banzai TABLE_ALIGNMENT_PATTERN = /text-align: (?<alignment>center|left|right)/.freeze def customize_allowlist(allowlist) + allowlist[:allow_comments] = context[:allow_comments] + # Allow table alignment; we allow specific text-align values in a # transformer below allowlist[:attributes]['th'] = %w[style] diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index 766715d9e39..489b4d21300 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -9,7 +9,7 @@ module Banzai module Filter # HTML Filter to highlight fenced code blocks # - class SyntaxHighlightFilter < HTML::Pipeline::Filter + class SyntaxHighlightFilter < TimeoutHtmlPipelineFilter include OutputSafety LANG_PARAMS_DELIMITER = ':' @@ -19,7 +19,7 @@ module Banzai CSS = 'pre:not([data-kroki-style]) > code:only-child' XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze - def call + def call_with_timeout doc.xpath(XPATH).each do |node| highlight_node(node) end diff --git a/lib/banzai/filter/timeout_html_pipeline_filter.rb b/lib/banzai/filter/timeout_html_pipeline_filter.rb new file mode 100644 index 00000000000..b9b71163ab1 --- /dev/null +++ b/lib/banzai/filter/timeout_html_pipeline_filter.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Banzai + module Filter + # HTML Filter that wraps a filter in a Gitlab::RenderTimeout. + # This way partial results can be returned, and the entire pipeline + # is not killed. + # + # This should not be used for any filter that must be allowed to complete, + # like a `ReferenceRedactorFilter` + # + class TimeoutHtmlPipelineFilter < HTML::Pipeline::Filter + RENDER_TIMEOUT = 10.seconds + + def call + Gitlab::RenderTimeout.timeout(foreground: RENDER_TIMEOUT) { call_with_timeout } + rescue Timeout::Error => e + class_name = self.class.name.demodulize + timeout_counter.increment(source: class_name) + Gitlab::ErrorTracking.track_exception(e, project_id: context[:project]&.id, class_name: class_name) + + # we've timed out, but some work may have already been completed, + # so go ahead and return the document + doc + end + + def call_with_timeout + raise NotImplementedError + end + + private + + def timeout_counter + Gitlab::Metrics.counter(:banzai_filter_timeouts_total, 'Count of the Banzai filters that time out') + end + end + end +end diff --git a/lib/banzai/pipeline/ascii_doc_pipeline.rb b/lib/banzai/pipeline/ascii_doc_pipeline.rb index afd5802de22..8764367426c 100644 --- a/lib/banzai/pipeline/ascii_doc_pipeline.rb +++ b/lib/banzai/pipeline/ascii_doc_pipeline.rb @@ -13,7 +13,7 @@ module Banzai Filter::ImageLazyLoadFilter, Filter::ImageLinkFilter, Filter::WikiLinkFilter, - Filter::SyntaxHighlightFilter, + Filter::SyntaxHighlightFilter, # this filter should remain next to last Filter::AsciiDocPostProcessingFilter ] end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 9b73e413d44..73065571849 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -22,6 +22,7 @@ module Banzai Filter::MermaidFilter, Filter::VideoLinkFilter, Filter::AudioLinkFilter, + Filter::AttributesFilter, Filter::ImageLazyLoadFilter, Filter::ImageLinkFilter, *metrics_filters, @@ -36,8 +37,9 @@ module Banzai Filter::CustomEmojiFilter, Filter::TaskListFilter, Filter::InlineDiffFilter, + Filter::InlineObservabilityFilter, Filter::SetDirectionFilter, - Filter::SyntaxHighlightFilter + Filter::SyntaxHighlightFilter # this filter should remain at the end ] end diff --git a/lib/banzai/pipeline/markup_pipeline.rb b/lib/banzai/pipeline/markup_pipeline.rb index 330914f7238..635d4c0884e 100644 --- a/lib/banzai/pipeline/markup_pipeline.rb +++ b/lib/banzai/pipeline/markup_pipeline.rb @@ -10,7 +10,7 @@ module Banzai Filter::ExternalLinkFilter, Filter::PlantumlFilter, Filter::KrokiFilter, - Filter::SyntaxHighlightFilter + Filter::SyntaxHighlightFilter # this filter should remain at the end ] end diff --git a/lib/banzai/pipeline/wiki_pipeline.rb b/lib/banzai/pipeline/wiki_pipeline.rb index caba9570ab9..96b6b9699f4 100644 --- a/lib/banzai/pipeline/wiki_pipeline.rb +++ b/lib/banzai/pipeline/wiki_pipeline.rb @@ -4,10 +4,8 @@ module Banzai module Pipeline class WikiPipeline < FullPipeline def self.filters - @filters ||= begin - super.insert_before(Filter::ImageLazyLoadFilter, Filter::GollumTagsFilter) + @filters ||= super.insert_before(Filter::ImageLazyLoadFilter, Filter::GollumTagsFilter) .insert_before(Filter::TaskListFilter, Filter::WikiLinkFilter) - end end end end diff --git a/lib/banzai/reference_parser/alert_parser.rb b/lib/banzai/reference_parser/alert_parser.rb index 7b864d26f67..676c0ac40ef 100644 --- a/lib/banzai/reference_parser/alert_parser.rb +++ b/lib/banzai/reference_parser/alert_parser.rb @@ -5,14 +5,18 @@ module Banzai class AlertParser < BaseParser self.reference_type = :alert + def self.reference_class + AlertManagement::Alert + end + def references_relation AlertManagement::Alert end private - def can_read_reference?(user, alert, node) - can?(user, :read_alert_management_alert, alert) + def can_read_reference?(user, project, node) + can?(user, :read_alert_management_alert, project) end end end diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index 19d91876892..88ce63a63fe 100644 --- a/lib/banzai/reference_parser/base_parser.rb +++ b/lib/banzai/reference_parser/base_parser.rb @@ -47,6 +47,14 @@ module Banzai @data_attribute ||= "data-#{reference_type.to_s.dasherize}" end + # Returns a model class to use as a reference. + # By default, the method does not take namespaces into account, + # thus parser classes can customize the reference class to use + # a model name with a namespace + def self.reference_class + reference_type.to_s.classify.constantize + end + # context - An instance of `Banzai::RenderContext`. def initialize(context) @context = context |