Class: Banzai::Filter::PlaceholdersPostFilter

Inherits:
HTML::Pipeline::Filter
  • Object
show all
Includes:
Concerns::PipelineTimingCheck, Concerns::TimeoutFilterHandler
Defined in:
lib/banzai/filter/placeholders_post_filter.rb

Overview

Replaces previously identified dynamic placeholders with current values. By performing this as a post-processing filter, we can show current information.

Defined Under Namespace

Classes: PlaceholderReplacer

Constant Summary collapse

CSS =

gitlab-glfm-markdown will detect possible placeholder values, and mark them with a ‘<span data-placeholder>` for text, or add data-placeholder to links or images. This allows us to specifically search for those nodes.

The syntax used is ‘%PLACEHOLDER`. Markdown processing ignores this syntax, so even links with embedded placeholders will get preserved.

'[data-placeholder]'
XPATH =
Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
FILTER_ITEM_LIMIT =
100
ALLOWED_URI_CONTEXT_ALL =
:all
ALLOWED_URI_CONTEXT_ALL_BUT_HOST =
:all_but_host
PLACEHOLDER_REPLACERS =

Variables that can be replaced. We handle them all dynamically in post-process as the values can change over time.

All placeholder replacements yield text, not HTML, and must be escaped or set as a text node’s content.

See PlaceholderReplacer for documentation on the metadata of each.

{
  'gitlab_server' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL) do
    Gitlab.config.gitlab.host
  end,
  'gitlab_pages_domain' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL) do
    Gitlab.config.pages.host
  end,
  'project_path' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL_BUT_HOST, uri_encode: false) do |context|
    # Rationale for `uri_encode: false`: the path is to be explicitly expandable, as many services
    # will generate URLs including a namespaced path for end-users.
    context[:project]&.full_path if Ability.allowed?(context[:current_user], :read_project, context[:project])
  end,
  'project_name' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL_BUT_HOST) do |context|
    context[:project]&.path if Ability.allowed?(context[:current_user], :read_project, context[:project])
  end,
  'project_id' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL_BUT_HOST) do |context|
    context[:project]&.id.to_s if Ability.allowed?(context[:current_user], :read_project, context[:project])
  end,
  'project_namespace' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL_BUT_HOST) do |context|
    if Ability.allowed?(context[:current_user], :read_project, context[:project])
      context[:project]&.project_namespace&.to_param
    end
  end,
  'project_title' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL_BUT_HOST) do |context|
    context[:project]&.title if Ability.allowed?(context[:current_user], :read_project, context[:project])
  end,
  'group_name' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL_BUT_HOST) do |context|
    group = context[:project]&.group || context[:group]
    group.name if Ability.allowed?(context[:current_user], :read_group, group)
  end,
  'default_branch' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL_BUT_HOST, uri_encode: false) do |context|
    # Rationale for `uri_encode: false`: GitLab displays URLs like
    #   http://gdk.test:3000/root/comrak/-/tree/kiv/dev
    # where "kiv/dev" is a branch name.  (In practice, as of writing, they generate a 404 when used!)
    # To support this kind of expansion, we must not percent-encode the branch name.
    if context[:project]&.repository_exists? &&
        Ability.allowed?(context[:current_user], :read_code, context[:project])
      context[:project]&.default_branch
    end
  end,
  'current_ref' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL_BUT_HOST) do |context|
    if context[:project]&.repository_exists? &&
        Ability.allowed?(context[:current_user], :read_code, context[:project])
      context[:ref]
    end
  end,
  'commit_sha' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL_BUT_HOST) do |context|
    if context[:project]&.repository_exists? &&
        Ability.allowed?(context[:current_user], :read_code, context[:project])
      context[:project]&.commit&.sha
    end
  end,
  'latest_tag' => PlaceholderReplacer.new(ALLOWED_URI_CONTEXT_ALL_BUT_HOST, uri_encode: false) do |context|
    if context[:project]&.repository_exists? &&
        Ability.allowed?(context[:current_user], :read_code, context[:project])
      # Rationale for `uri_encode: false`: GitLab uses URLs like
      #   http://gdk.test:3000/root/comrak/-/tree/a/b/c
      # where "a/b/c" is a tag name.  (These actually work, unlike branches.)
      # To support this kind of expansion, we must not percent-encode the tag name.
      TagsFinder.new(context[:project].repository, per_page: 1, sort: 'updated_desc')
        &.execute&.first&.name
    end
  end
}.freeze
PLACEHOLDERS_REGEX =
/(#{PLACEHOLDER_REPLACERS.keys.map { |p| Regexp.escape(p) }.join('|')})/
PLACEHOLDERS_FULL_ANCHORED_REGEX =
/\A%\{#{PLACEHOLDERS_REGEX}}\z/

Constants included from Concerns::TimeoutFilterHandler

Concerns::TimeoutFilterHandler::COMPLEX_MARKDOWN_MESSAGE, Concerns::TimeoutFilterHandler::RENDER_TIMEOUT, Concerns::TimeoutFilterHandler::SANITIZATION_RENDER_TIMEOUT

Constants included from Concerns::PipelineTimingCheck

Concerns::PipelineTimingCheck::MAX_PIPELINE_SECONDS

Instance Method Summary collapse

Methods included from Concerns::PipelineTimingCheck

#exceeded_pipeline_max?

Instance Method Details

#callObject



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/banzai/filter/placeholders_post_filter.rb', line 177

def call
  return doc unless context[:project]&.markdown_placeholders_feature_flag_enabled? ||
    context[:group]&.markdown_placeholders_feature_flag_enabled?

  return doc if context[:disable_placeholders] || context[:broadcast_message_placeholders]

  doc.xpath(XPATH).each_with_index do |node, index|
    break if Banzai::Filter.filter_item_limit_exceeded?(index, limit: FILTER_ITEM_LIMIT)

    case node.name
    when 'span'
      replace_span_placeholder(node)
    when 'a'
      replace_link_placeholders(node)
    when 'img'
      replace_image_placeholders(node)
    else
      next
    end
  end

  doc
end