debian-mirror-gitlab/lib/banzai/filter/relative_link_filter.rb

272 lines
7 KiB
Ruby
Raw Normal View History

2018-11-18 11:00:15 +05:30
# frozen_string_literal: true
2015-09-11 14:41:01 +05:30
require 'uri'
2015-12-23 02:04:40 +05:30
module Banzai
module Filter
2018-03-17 18:26:18 +05:30
# HTML filter that "fixes" relative links to uploads or files in a repository.
2015-09-11 14:41:01 +05:30
#
# Context options:
# :commit
2018-03-17 18:26:18 +05:30
# :group
2019-09-04 21:01:54 +05:30
# :current_user
2015-09-11 14:41:01 +05:30
# :project
# :project_wiki
# :ref
# :requested_path
class RelativeLinkFilter < HTML::Pipeline::Filter
2018-03-17 18:26:18 +05:30
include Gitlab::Utils::StrongMemoize
2015-09-11 14:41:01 +05:30
2018-03-17 18:26:18 +05:30
def call
2019-09-04 21:01:54 +05:30
return doc if context[:system_note]
2018-03-17 18:26:18 +05:30
clear_memoization(:linkable_files)
2019-12-21 20:55:43 +05:30
clear_memoization(:linkable_attributes)
2016-06-22 15:30:34 +05:30
2019-12-21 20:55:43 +05:30
load_uri_types
2015-09-11 14:41:01 +05:30
2019-12-21 20:55:43 +05:30
linkable_attributes.each do |attr|
process_link_attr(attr)
2015-09-11 14:41:01 +05:30
end
doc
end
protected
2019-12-21 20:55:43 +05:30
def load_uri_types
return unless linkable_files?
return unless linkable_attributes.present?
return {} unless repository
@uri_types = request_path.present? ? get_uri_types([request_path]) : {}
paths = linkable_attributes.flat_map do |attr|
[get_uri(attr).to_s, relative_file_path(get_uri(attr))]
end
paths.reject!(&:blank?)
paths.uniq!
@uri_types.merge!(get_uri_types(paths))
end
2015-09-11 14:41:01 +05:30
def linkable_files?
2018-03-17 18:26:18 +05:30
strong_memoize(:linkable_files) do
context[:project_wiki].nil? && repository.try(:exists?) && !repository.empty?
end
2015-09-11 14:41:01 +05:30
end
2019-12-21 20:55:43 +05:30
def linkable_attributes
strong_memoize(:linkable_attributes) do
attrs = []
attrs += doc.search('a:not(.gfm)').map do |el|
el.attribute('href')
end
attrs += doc.search('img, video, audio').flat_map do |el|
[el.attribute('src'), el.attribute('data-src')]
end
attrs.reject do |attr|
attr.blank? || attr.value.start_with?('//')
end
end
end
def get_uri_types(paths)
return {} if paths.empty?
uri_types = Hash[paths.collect { |name| [name, nil] }]
get_blob_types(paths).each do |name, type|
if type == :blob
blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: name), project)
uri_types[name] = blob.image? || blob.video? || blob.audio? ? :raw : :blob
else
uri_types[name] = type
end
end
uri_types
end
2015-09-11 14:41:01 +05:30
2019-12-21 20:55:43 +05:30
def get_blob_types(paths)
revision_paths = paths.collect do |path|
[current_commit.sha, path.chomp("/")]
end
Gitlab::GitalyClient::BlobService.new(repository).get_blob_types(revision_paths, 1)
end
def get_uri(html_attr)
uri = URI(html_attr.value)
uri if uri.relative? && uri.path.present?
rescue URI::Error, Addressable::URI::InvalidURIError
end
def process_link_attr(html_attr)
2018-03-17 18:26:18 +05:30
if html_attr.value.start_with?('/uploads/')
process_link_to_upload_attr(html_attr)
2019-10-12 21:52:04 +05:30
elsif linkable_files? && repo_visible_to_user?
2018-03-17 18:26:18 +05:30
process_link_to_repository_attr(html_attr)
end
end
def process_link_to_upload_attr(html_attr)
path_parts = [Addressable::URI.unescape(html_attr.value)]
2019-09-30 21:07:59 +05:30
if project
2018-03-17 18:26:18 +05:30
path_parts.unshift(relative_url_root, project.full_path)
2019-09-30 21:07:59 +05:30
elsif group
path_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
2019-03-02 22:35:43 +05:30
else
path_parts.unshift(relative_url_root)
2018-03-17 18:26:18 +05:30
end
2018-12-05 23:21:45 +05:30
begin
path = Addressable::URI.escape(File.join(*path_parts))
rescue Addressable::URI::InvalidURIError
return
end
2018-03-17 18:26:18 +05:30
html_attr.value =
if context[:only_path]
path
else
Addressable::URI.join(Gitlab.config.gitlab.base_url, path).to_s
end
end
def process_link_to_repository_attr(html_attr)
2015-09-11 14:41:01 +05:30
uri = URI(html_attr.value)
2019-12-21 20:55:43 +05:30
2015-09-11 14:41:01 +05:30
if uri.relative? && uri.path.present?
html_attr.value = rebuild_relative_uri(uri).to_s
end
2018-03-17 18:26:18 +05:30
rescue URI::Error, Addressable::URI::InvalidURIError
2015-09-11 14:41:01 +05:30
# noop
end
def rebuild_relative_uri(uri)
2019-12-21 20:55:43 +05:30
file_path = nested_file_path_if_exists(uri)
2015-09-11 14:41:01 +05:30
uri.path = [
relative_url_root,
2018-03-17 18:26:18 +05:30
project.full_path,
2016-06-22 15:30:34 +05:30
uri_type(file_path),
2018-05-09 12:01:36 +05:30
Addressable::URI.escape(ref).gsub('#', '%23'),
2017-08-17 22:00:37 +05:30
Addressable::URI.escape(file_path)
2015-09-11 14:41:01 +05:30
].compact.join('/').squeeze('/').chomp('/')
uri
end
2019-12-21 20:55:43 +05:30
def nested_file_path_if_exists(uri)
path = cleaned_file_path(uri)
nested_path = relative_file_path(uri)
2015-09-11 14:41:01 +05:30
file_exists?(nested_path) ? nested_path : path
end
2019-12-21 20:55:43 +05:30
def cleaned_file_path(uri)
Addressable::URI.unescape(uri.path).scrub.delete("\0").chomp("/")
end
def relative_file_path(uri)
return if uri.nil?
build_relative_path(cleaned_file_path(uri), request_path)
end
def request_path
return unless context[:requested_path]
Addressable::URI.unescape(context[:requested_path]).chomp("/")
end
2015-09-25 12:07:36 +05:30
# Convert a relative path into its correct location based on the currently
# requested path
#
# path - Relative path String
# request_path - Currently-requested path String
#
# Examples:
#
# # File in the same directory as the current path
# build_relative_path("users.md", "doc/api/README.md")
# # => "doc/api/users.md"
#
# # File in the same directory, which is also the current path
# build_relative_path("users.md", "doc/api")
# # => "doc/api/users.md"
#
# # Going up one level to a different directory
# build_relative_path("../update/7.14-to-8.0.md", "doc/api/README.md")
# # => "doc/update/7.14-to-8.0.md"
#
# Returns a String
def build_relative_path(path, request_path)
2015-09-11 14:41:01 +05:30
return request_path if path.empty?
return path unless request_path
2016-09-13 17:45:13 +05:30
return path[1..-1] if path.start_with?('/')
2015-09-11 14:41:01 +05:30
parts = request_path.split('/')
2019-12-21 20:55:43 +05:30
2016-06-22 15:30:34 +05:30
parts.pop if uri_type(request_path) != :tree
2015-09-25 12:07:36 +05:30
2016-09-13 17:45:13 +05:30
path.sub!(%r{\A\./}, '')
while path.start_with?('../')
2015-09-25 12:07:36 +05:30
parts.pop
path.sub!('../', '')
end
2015-09-11 14:41:01 +05:30
parts.push(path).join('/')
end
def file_exists?(path)
2019-12-21 20:55:43 +05:30
path.present? && uri_type(path).present?
2015-09-11 14:41:01 +05:30
end
2016-06-22 15:30:34 +05:30
def uri_type(path)
2019-12-21 20:55:43 +05:30
@uri_types[path] == :unknown ? "" : @uri_types[path]
2015-09-11 14:41:01 +05:30
end
2016-06-22 15:30:34 +05:30
def current_commit
2016-09-13 17:45:13 +05:30
@current_commit ||= context[:commit] || repository.commit(ref)
2015-09-11 14:41:01 +05:30
end
def relative_url_root
Gitlab.config.gitlab.relative_url_root.presence || '/'
end
2019-10-12 21:52:04 +05:30
def repo_visible_to_user?
project && Ability.allowed?(current_user, :download_code, project)
2019-09-04 21:01:54 +05:30
end
2015-09-11 14:41:01 +05:30
def ref
2018-03-17 18:26:18 +05:30
context[:ref] || project.default_branch
end
def group
context[:group]
end
def project
context[:project]
2015-09-11 14:41:01 +05:30
end
2019-09-04 21:01:54 +05:30
def current_user
context[:current_user]
end
2015-09-11 14:41:01 +05:30
def repository
2018-03-17 18:26:18 +05:30
@repository ||= project&.repository
2015-09-11 14:41:01 +05:30
end
end
end
end