158 lines
4.0 KiB
Ruby
158 lines
4.0 KiB
Ruby
require 'gitlab/markdown'
|
|
require 'html/pipeline/filter'
|
|
require 'uri'
|
|
|
|
module Gitlab
|
|
module Markdown
|
|
# HTML filter that "fixes" relative links to files in a repository.
|
|
#
|
|
# Context options:
|
|
# :commit
|
|
# :project
|
|
# :project_wiki
|
|
# :ref
|
|
# :requested_path
|
|
class RelativeLinkFilter < HTML::Pipeline::Filter
|
|
def call
|
|
return doc unless linkable_files?
|
|
|
|
doc.search('a').each do |el|
|
|
process_link_attr el.attribute('href')
|
|
end
|
|
|
|
doc.search('img').each do |el|
|
|
process_link_attr el.attribute('src')
|
|
end
|
|
|
|
doc
|
|
end
|
|
|
|
protected
|
|
|
|
def linkable_files?
|
|
context[:project_wiki].nil? && repository.try(:exists?) && !repository.empty?
|
|
end
|
|
|
|
def process_link_attr(html_attr)
|
|
return if html_attr.blank?
|
|
|
|
uri = URI(html_attr.value)
|
|
if uri.relative? && uri.path.present?
|
|
html_attr.value = rebuild_relative_uri(uri).to_s
|
|
end
|
|
rescue URI::Error
|
|
# noop
|
|
end
|
|
|
|
def rebuild_relative_uri(uri)
|
|
file_path = relative_file_path(uri.path)
|
|
|
|
uri.path = [
|
|
relative_url_root,
|
|
context[:project].path_with_namespace,
|
|
path_type(file_path),
|
|
ref || 'master', # assume that if no ref exists we can point to master
|
|
file_path
|
|
].compact.join('/').squeeze('/').chomp('/')
|
|
|
|
uri
|
|
end
|
|
|
|
def relative_file_path(path)
|
|
nested_path = build_relative_path(path, context[:requested_path])
|
|
file_exists?(nested_path) ? nested_path : path
|
|
end
|
|
|
|
# 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)
|
|
return request_path if path.empty?
|
|
return path unless request_path
|
|
|
|
parts = request_path.split('/')
|
|
parts.pop if path_type(request_path) != 'tree'
|
|
|
|
while parts.length > 1 && path.start_with?('../')
|
|
parts.pop
|
|
path.sub!('../', '')
|
|
end
|
|
|
|
parts.push(path).join('/')
|
|
end
|
|
|
|
def file_exists?(path)
|
|
return false if path.nil?
|
|
repository.blob_at(current_sha, path).present? ||
|
|
repository.tree(current_sha, path).entries.any?
|
|
end
|
|
|
|
# Get the type of the given path
|
|
#
|
|
# path - String path to check
|
|
#
|
|
# Examples:
|
|
#
|
|
# path_type('doc/README.md') # => 'blob'
|
|
# path_type('doc/logo.png') # => 'raw'
|
|
# path_type('doc/api') # => 'tree'
|
|
#
|
|
# Returns a String
|
|
def path_type(path)
|
|
unescaped_path = Addressable::URI.unescape(path)
|
|
|
|
if tree?(unescaped_path)
|
|
'tree'
|
|
elsif image?(unescaped_path)
|
|
'raw'
|
|
else
|
|
'blob'
|
|
end
|
|
end
|
|
|
|
def tree?(path)
|
|
repository.tree(current_sha, path).entries.any?
|
|
end
|
|
|
|
def image?(path)
|
|
repository.blob_at(current_sha, path).try(:image?)
|
|
end
|
|
|
|
def current_sha
|
|
context[:commit].try(:id) ||
|
|
ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha
|
|
end
|
|
|
|
def relative_url_root
|
|
Gitlab.config.gitlab.relative_url_root.presence || '/'
|
|
end
|
|
|
|
def ref
|
|
context[:ref]
|
|
end
|
|
|
|
def repository
|
|
context[:project].try(:repository)
|
|
end
|
|
end
|
|
end
|
|
end
|