diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-20 13:43:29 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-20 13:43:29 +0300 |
commit | 3b1af5cc7ed2666ff18b718ce5d30fa5a2756674 (patch) | |
tree | 3bc4a40e0ee51ec27eabf917c537033c0c5b14d4 /lib/banzai/filter | |
parent | 9bba14be3f2c211bf79e15769cd9b77bc73a13bc (diff) |
Add latest changes from gitlab-org/gitlab@16-1-stable-eev16.1.0-rc42
Diffstat (limited to 'lib/banzai/filter')
-rw-r--r-- | lib/banzai/filter/autolink_filter.rb | 2 | ||||
-rw-r--r-- | lib/banzai/filter/dollar_math_post_filter.rb | 4 | ||||
-rw-r--r-- | lib/banzai/filter/inline_alert_metrics_filter.rb | 47 | ||||
-rw-r--r-- | lib/banzai/filter/inline_cluster_metrics_filter.rb | 40 | ||||
-rw-r--r-- | lib/banzai/filter/inline_diff_filter.rb | 4 | ||||
-rw-r--r-- | lib/banzai/filter/inline_embeds_filter.rb | 93 | ||||
-rw-r--r-- | lib/banzai/filter/inline_grafana_metrics_filter.rb | 79 | ||||
-rw-r--r-- | lib/banzai/filter/inline_metrics_filter.rb | 36 | ||||
-rw-r--r-- | lib/banzai/filter/inline_metrics_redactor_filter.rb | 154 | ||||
-rw-r--r-- | lib/banzai/filter/references/reference_filter.rb | 4 | ||||
-rw-r--r-- | lib/banzai/filter/references/user_reference_filter.rb | 2 | ||||
-rw-r--r-- | lib/banzai/filter/sanitization_filter.rb | 2 | ||||
-rw-r--r-- | lib/banzai/filter/spaced_link_filter.rb | 2 | ||||
-rw-r--r-- | lib/banzai/filter/table_of_contents_filter.rb | 4 |
14 files changed, 12 insertions, 461 deletions
diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb index a86c1bb2892..336d60055e2 100644 --- a/lib/banzai/filter/autolink_filter.rb +++ b/lib/banzai/filter/autolink_filter.rb @@ -40,7 +40,7 @@ module Banzai IGNORE_PARENTS = %w(a code kbd pre script style).to_set # The XPath query to use for finding text nodes to parse. - TEXT_QUERY = %Q(descendant-or-self::text()[ + TEXT_QUERY = %(descendant-or-self::text()[ not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')}) and contains(., '://') ]) diff --git a/lib/banzai/filter/dollar_math_post_filter.rb b/lib/banzai/filter/dollar_math_post_filter.rb index 76f69a66e8d..b3c230131e4 100644 --- a/lib/banzai/filter/dollar_math_post_filter.rb +++ b/lib/banzai/filter/dollar_math_post_filter.rb @@ -20,13 +20,13 @@ module Banzai DOLLAR_INLINE_UNTRUSTED = '(?P<matched>\$(?P<math>(?:\S[^$\n]*?\S|[^$\s]))\$)(?:[^\d]|$)' DOLLAR_INLINE_UNTRUSTED_REGEX = - Gitlab::UntrustedRegexp.new(DOLLAR_INLINE_UNTRUSTED, multiline: false) + Gitlab::UntrustedRegexp.new(DOLLAR_INLINE_UNTRUSTED, multiline: false).freeze # Corresponds to the "$$...$$" syntax DOLLAR_DISPLAY_INLINE_UNTRUSTED = '(?P<matched>\$\$\ *(?P<math>[^$\n]+?)\ *\$\$)' DOLLAR_DISPLAY_INLINE_UNTRUSTED_REGEX = - Gitlab::UntrustedRegexp.new(DOLLAR_DISPLAY_INLINE_UNTRUSTED, multiline: false) + Gitlab::UntrustedRegexp.new(DOLLAR_DISPLAY_INLINE_UNTRUSTED, multiline: false).freeze # Order dependent. Handle the `$$` syntax before the `$` syntax DOLLAR_MATH_PIPELINE = [ diff --git a/lib/banzai/filter/inline_alert_metrics_filter.rb b/lib/banzai/filter/inline_alert_metrics_filter.rb deleted file mode 100644 index a6140d1ac81..00000000000 --- a/lib/banzai/filter/inline_alert_metrics_filter.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module Banzai - module Filter - # HTML filter that inserts a placeholder element for each - # reference to an alert dashboard. - class InlineAlertMetricsFilter < ::Banzai::Filter::InlineEmbedsFilter - include ::Gitlab::Routing - # Search params for selecting alert metrics links. A few - # simple checks is enough to boost performance without - # the cost of doing a full regex match. - def xpath_search - "descendant-or-self::a[contains(@href,'metrics_dashboard') and \ - contains(@href,'prometheus/alerts') and \ - starts-with(@href, '#{gitlab_domain}')]" - end - - # Regular expression matching alert dashboard urls - def link_pattern - ::Gitlab::Metrics::Dashboard::Url.alert_regex - end - - private - - # Endpoint FE should hit to collect the appropriate - # chart information - def metrics_dashboard_url(params) - metrics_dashboard_namespace_project_prometheus_alert_url( - params['namespace'], - params['project'], - params['alert'], - embedded: true, - format: :json, - **query_params(params['url']) - ) - end - - # Parses query params out from full url string into hash. - # - # Ex) 'https://<root>/<project>/metrics_dashboard?title=Title&group=Group' - # --> { title: 'Title', group: 'Group' } - def query_params(url) - ::Gitlab::Metrics::Dashboard::Url.parse_query(url) - end - end - end -end diff --git a/lib/banzai/filter/inline_cluster_metrics_filter.rb b/lib/banzai/filter/inline_cluster_metrics_filter.rb deleted file mode 100644 index a696d3a6f9c..00000000000 --- a/lib/banzai/filter/inline_cluster_metrics_filter.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Banzai - module Filter - class InlineClusterMetricsFilter < ::Banzai::Filter::InlineEmbedsFilter - def embed_params(node) - url = node['href'] - @query_params = query_params(url) - return unless [:group, :title, :y_label].all? do |param| - @query_params.include?(param) - end - - link_pattern.match(url) { |m| m.named_captures }.symbolize_keys - end - - def xpath_search - "descendant-or-self::a[contains(@href,'clusters') and \ - starts-with(@href, '#{gitlab_domain}')]" - end - - def link_pattern - ::Gitlab::Metrics::Dashboard::Url.clusters_regex - end - - def metrics_dashboard_url(params) - ::Gitlab::Routing.url_helpers.metrics_dashboard_namespace_project_cluster_url( - params[:namespace], - params[:project], - params[:cluster_id], - # Only Project clusters are supported for now - # admin and group cluster types may be supported in the future - cluster_type: :project, - embedded: true, - format: :json, - **@query_params - ) - end - end - end -end diff --git a/lib/banzai/filter/inline_diff_filter.rb b/lib/banzai/filter/inline_diff_filter.rb index 2a43540934c..fc77984f135 100644 --- a/lib/banzai/filter/inline_diff_filter.rb +++ b/lib/banzai/filter/inline_diff_filter.rb @@ -8,11 +8,11 @@ module Banzai INLINE_DIFF_DELETION_UNTRUSTED = '(?:\[\-(.*?)\-\]|\{\-(.*?)\-\})' INLINE_DIFF_DELETION_UNTRUSTED_REGEX = - Gitlab::UntrustedRegexp.new(INLINE_DIFF_DELETION_UNTRUSTED, multiline: false) + Gitlab::UntrustedRegexp.new(INLINE_DIFF_DELETION_UNTRUSTED, multiline: false).freeze INLINE_DIFF_ADDITION_UNTRUSTED = '(?:\[\+(.*?)\+\]|\{\+(.*?)\+\})' INLINE_DIFF_ADDITION_UNTRUSTED_REGEX = - Gitlab::UntrustedRegexp.new(INLINE_DIFF_ADDITION_UNTRUSTED, multiline: false) + Gitlab::UntrustedRegexp.new(INLINE_DIFF_ADDITION_UNTRUSTED, multiline: false).freeze def call doc.xpath('descendant-or-self::text()').each do |node| diff --git a/lib/banzai/filter/inline_embeds_filter.rb b/lib/banzai/filter/inline_embeds_filter.rb deleted file mode 100644 index a16166123f8..00000000000 --- a/lib/banzai/filter/inline_embeds_filter.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -module Banzai - module Filter - # HTML filter that inserts a node for each occurrence of - # a given link format. To transform references to DB - # resources in place, prefer to inherit from AbstractReferenceFilter. - class InlineEmbedsFilter < HTML::Pipeline::Filter - # Find every relevant link, create a new node based on - # the link, and insert this node after any html content - # surrounding the link. - def call - return doc if Feature.enabled?(:remove_monitor_metrics) - - doc.xpath(xpath_search).each do |node| - next unless element = element_to_embed(node) - - # We want this to follow any surrounding content. For example, - # if a link is inline in a paragraph. - node.parent.children.last.add_next_sibling(element) - end - - doc - end - - # Child class must provide the metrics_dashboard_url. - # - # Return a Nokogiri::XML::Element to embed in the - # markdown which provides a url to the metric_dashboard endpoint where - # data can be requested through a prometheus proxy. InlineMetricsRedactorFilter - # is responsible for permissions to see this div (and relies on the class 'js-render-metrics' ). - def create_element(params) - doc.document.create_element( - 'div', - class: 'js-render-metrics', - 'data-dashboard-url': metrics_dashboard_url(params) - ) - end - - # Implement in child class unless overriding #embed_params - # - # Returns the regex pattern used to filter - # to only matching urls. - def link_pattern - end - - # Returns the xpath query string used to select nodes - # from the html document on which the embed is based. - # - # Override to select nodes other than links. - def xpath_search - 'descendant-or-self::a[@href]' - end - - # Creates a new element based on the parameters - # obtained from the target link - def element_to_embed(node) - return unless params = embed_params(node) - - create_element(params) - end - - # Returns a hash of named parameters based on the - # provided regex with string keys. - # - # Override to select nodes other than links. - def embed_params(node) - url = node['href'] - - link_pattern.match(url) { |m| m.named_captures } - end - - # Parses query params out from full url string into hash. - # - # Ex) 'https://<root>/<project>/<environment>/metrics?title=Title&group=Group' - # --> { title: 'Title', group: 'Group' } - def query_params(url) - Gitlab::Metrics::Dashboard::Url.parse_query(url) - end - - # Implement in child class. - # - # Provides a full url to request the relevant panels of metric data. - def metrics_dashboard_url - raise NotImplementedError - end - - def gitlab_domain - ::Gitlab.config.gitlab.url - end - end - end -end diff --git a/lib/banzai/filter/inline_grafana_metrics_filter.rb b/lib/banzai/filter/inline_grafana_metrics_filter.rb deleted file mode 100644 index 07bde9858e8..00000000000 --- a/lib/banzai/filter/inline_grafana_metrics_filter.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -module Banzai - module Filter - # HTML filter that inserts a placeholder element for each - # reference to a grafana dashboard. - class InlineGrafanaMetricsFilter < Banzai::Filter::InlineEmbedsFilter - # Placeholder element for the frontend to use as an - # injection point for charts. - def create_element(params) - begin_loading_dashboard(params[:url]) - - super - end - - # @return [Hash<Symbol, String>] with keys :grafana_url, :start, and :end - def embed_params(node) - query_params = Gitlab::Metrics::Dashboard::Url.parse_query(node['href']) - - time_window = Grafana::TimeWindow.new(query_params[:from], query_params[:to]) - url = url_with_window(node['href'], query_params, time_window.in_milliseconds) - - { grafana_url: url }.merge(time_window.formatted) - end - - # Selects any links with an href contains the configured - # grafana domain for the project - def xpath_search - return unless grafana_url.present? - - %(descendant-or-self::a[starts-with(@href, '#{grafana_url}')]) - end - - private - - def project - context[:project] - end - - def grafana_url - project&.grafana_integration&.grafana_url - end - - def metrics_dashboard_url(params) - Gitlab::Routing.url_helpers.project_grafana_api_metrics_dashboard_url( - project, - embedded: true, - **params - ) - end - - # If the provided url is missing time window parameters, - # this inserts the default window into the url, allowing - # the embed service to correctly format prometheus - # queries during embed processing. - # - # @param url [String] - # @param query_params [Hash<Symbol, String>] - # @param time_window_params [Hash<Symbol, Integer>] - # @return [String] - def url_with_window(url, query_params, time_window_params) - uri = URI(url) - uri.query = time_window_params.merge(query_params).to_query - - uri.to_s - end - - # Fetches a dashboard and caches the result for the - # FE to fetch quickly while rendering charts - def begin_loading_dashboard(url) - ::Gitlab::Metrics::Dashboard::Finder.find( - project, - embedded: true, - grafana_url: url - ) - end - end - end -end diff --git a/lib/banzai/filter/inline_metrics_filter.rb b/lib/banzai/filter/inline_metrics_filter.rb deleted file mode 100644 index 2872ad7b632..00000000000 --- a/lib/banzai/filter/inline_metrics_filter.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module Banzai - module Filter - # HTML filter that inserts a placeholder element for each - # reference to a metrics dashboard. - class InlineMetricsFilter < Banzai::Filter::InlineEmbedsFilter - # Search params for selecting metrics links. A few - # simple checks is enough to boost performance without - # the cost of doing a full regex match. - def xpath_search - "descendant-or-self::a[contains(@href,'metrics') and \ - starts-with(@href, '#{gitlab_domain}')]" - end - - # Regular expression matching metrics urls - def link_pattern - Gitlab::Metrics::Dashboard::Url.metrics_regex - end - - private - - # Endpoint FE should hit to collect the appropriate - # chart information - def metrics_dashboard_url(params) - Gitlab::Metrics::Dashboard::Url.build_dashboard_url( - params['namespace'], - params['project'], - params['environment'], - embedded: true, - **query_params(params['url']).except(:environment) - ) - end - end - end -end diff --git a/lib/banzai/filter/inline_metrics_redactor_filter.rb b/lib/banzai/filter/inline_metrics_redactor_filter.rb deleted file mode 100644 index b256815ae84..00000000000 --- a/lib/banzai/filter/inline_metrics_redactor_filter.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true - -module Banzai - module Filter - # HTML filter that removes embeded elements that the current user does - # not have permission to view. - class InlineMetricsRedactorFilter < HTML::Pipeline::Filter - include Gitlab::Utils::StrongMemoize - - METRICS_CSS_CLASS = '.js-render-metrics' - XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(METRICS_CSS_CLASS).freeze - EMBED_LIMIT = 100 - - Route = Struct.new(:regex, :permission) - Embed = Struct.new(:project_path, :permission) - - # Finds all embeds based on the css class the FE - # uses to identify the embedded content, removing - # only unnecessary nodes. - def call - nodes.each do |node| - embed = embeds_by_node[node] - user_has_access = user_access_by_embed[embed] - - node.remove unless user_has_access - end - - doc - end - - private - - def user - context[:current_user] - end - - # Returns all nodes which the FE will identify as - # a metrics embed placeholder element - # - # Removes any nodes beyond the first 100 - # - # @return [Nokogiri::XML::NodeSet] - def nodes - strong_memoize(:nodes) do - nodes = doc.xpath(XPATH) - nodes.drop(EMBED_LIMIT).each(&:remove) - - nodes - end - end - - # Maps a node to key properties of an embed. - # Memoized so we only need to run the regex to get - # the project full path from the url once per node. - # - # @return [Hash<Nokogiri::XML::Node, Embed>] - def embeds_by_node - strong_memoize(:embeds_by_node) do - nodes.each_with_object({}) do |node, embeds| - embed = Embed.new - url = node.attribute('data-dashboard-url').to_s - - permissions_by_route.each do |route| - set_path_and_permission(embed, url, route.regex, route.permission) unless embed.permission - end - - embeds[node] = embed if embed.permission - end - end - end - - def permissions_by_route - [ - Route.new( - ::Gitlab::Metrics::Dashboard::Url.metrics_regex, - :read_environment - ), - Route.new( - ::Gitlab::Metrics::Dashboard::Url.grafana_regex, - :read_project - ), - Route.new( - ::Gitlab::Metrics::Dashboard::Url.clusters_regex, - :read_cluster - ), - Route.new( - ::Gitlab::Metrics::Dashboard::Url.alert_regex, - :read_prometheus_alerts - ) - ] - end - - # Attempts to determine the path and permission attributes - # of a url based on expected dashboard url formats and - # sets the attributes on an Embed object - # - # @param embed [Embed] - # @param url [String] - # @param regex [RegExp] - # @param permission [Symbol] - def set_path_and_permission(embed, url, regex, permission) - return unless path = regex.match(url) do |m| - "#{$~[:namespace]}/#{$~[:project]}" - end - - embed.project_path = path - embed.permission = permission - end - - # Returns a mapping representing whether the current user - # has permission to view the embed for the project. - # Determined in a batch - # - # @return [Hash<Embed, Boolean>] - def user_access_by_embed - strong_memoize(:user_access_by_embed) do - unique_embeds.each_with_object({}) do |embed, access| - project = projects_by_path[embed.project_path] - - access[embed] = Ability.allowed?(user, embed.permission, project) - end - end - end - - # Returns a unique list of embeds - # - # @return [Array<Embed>] - def unique_embeds - embeds_by_node.values.uniq - end - - # Maps a project's full path to a Project object. - # Contains all of the Projects referenced in the - # metrics placeholder elements of the current document - # - # @return [Hash<String, Project>] - def projects_by_path - strong_memoize(:projects_by_path) do - Project.eager_load(:route, namespace: [:route]) - .where_full_path_in(unique_project_paths) - .index_by(&:full_path) - end - end - - # Returns a list of the full_paths of every project which - # has an embed in the doc - # - # @return [Array<String>] - def unique_project_paths - embeds_by_node.values.map(&:project_path).uniq - end - end - end -end diff --git a/lib/banzai/filter/references/reference_filter.rb b/lib/banzai/filter/references/reference_filter.rb index 37734f6a45a..a687ae2882e 100644 --- a/lib/banzai/filter/references/reference_filter.rb +++ b/lib/banzai/filter/references/reference_filter.rb @@ -143,7 +143,7 @@ module Banzai attributes.delete(:original) if context[:no_original_data] attributes.map do |key, value| - %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") + %(data-#{key.to_s.dasherize}="#{escape_once(value)}") end .join(' ') .prepend(reference_type_attribute) @@ -251,7 +251,7 @@ module Banzai end def query - @query ||= %Q{descendant-or-self::text()[not(#{ignore_ancestor_query})] + @query ||= %{descendant-or-self::text()[not(#{ignore_ancestor_query})] | descendant-or-self::a[ not(contains(concat(" ", @class, " "), " gfm ")) and not(@href = "") ]} diff --git a/lib/banzai/filter/references/user_reference_filter.rb b/lib/banzai/filter/references/user_reference_filter.rb index 5983036a8e5..d6b6fdb7149 100644 --- a/lib/banzai/filter/references/user_reference_filter.rb +++ b/lib/banzai/filter/references/user_reference_filter.rb @@ -45,7 +45,7 @@ module Banzai # have `gfm` and `gfm-project_member` class names attached for styling. def object_link_filter(text, pattern, link_content: nil, link_reference: false) references_in(text, pattern) do |match, username| - if username == 'all' && !skip_project_check? + if Feature.disabled?(:disable_all_mention) && username == 'all' && !skip_project_check? link_to_all(link_content: link_content) else cached_call(:banzai_url_for_object, match, path: [User, username.downcase]) do diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index b119c2ffccf..c2cad237d6f 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -34,7 +34,7 @@ module Banzai # Allow section elements with data-footnotes attribute allowlist[:elements].push('section') allowlist[:attributes]['section'] = %w(data-footnotes) - allowlist[:attributes]['a'].push('data-footnote-ref', 'data-footnote-backref') + allowlist[:attributes]['a'].push('data-footnote-ref', 'data-footnote-backref', 'data-footnote-backref-idx') allowlist end diff --git a/lib/banzai/filter/spaced_link_filter.rb b/lib/banzai/filter/spaced_link_filter.rb index f8d03fd6e50..d370a585271 100644 --- a/lib/banzai/filter/spaced_link_filter.rb +++ b/lib/banzai/filter/spaced_link_filter.rb @@ -42,7 +42,7 @@ module Banzai IGNORE_PARENTS = %w(a code kbd pre script style).to_set # The XPath query to use for finding text nodes to parse. - TEXT_QUERY = %Q(descendant-or-self::text()[ + TEXT_QUERY = %(descendant-or-self::text()[ not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')}) and contains(., ']\(') ]) diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb index d76009d08e1..de3e978b320 100644 --- a/lib/banzai/filter/table_of_contents_filter.rb +++ b/lib/banzai/filter/table_of_contents_filter.rb @@ -55,7 +55,7 @@ module Banzai def anchor_tag(href) escaped_href = CGI.escape(href) # account for non-ASCII characters - %Q{<a id="user-content-#{href}" class="anchor" href="##{escaped_href}" aria-hidden="true"></a>} + %{<a id="user-content-#{href}" class="anchor" href="##{escaped_href}" aria-hidden="true"></a>} end def push_toc(children, root: false) @@ -69,7 +69,7 @@ module Banzai end def push_anchor(header_node) - result[:toc] << %Q{<li><a href="##{header_node.href}">#{header_node.text}</a>} + result[:toc] << %{<li><a href="##{header_node.href}">#{header_node.text}</a>} push_toc(header_node.children) result[:toc] << '</li>' end |