summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorRobert Speicher <rspeicher@gmail.com>2015-04-27 18:54:13 -0400
committerRobert Speicher <rspeicher@gmail.com>2015-04-30 16:35:25 -0400
commitaa2cc670fe2c9de772c82d90df4ee2d8a77c23fc (patch)
tree403c34f8589864232db009a4960c7041d3a456c4 /lib
parent2d9edcada5135d1f72a2c284e93d8e5b18e00008 (diff)
downloadgitlab-ce-aa2cc670fe2c9de772c82d90df4ee2d8a77c23fc.tar.gz
Add Gitlab::Markdown::AutolinkFilter
Diffstat (limited to 'lib')
-rw-r--r--lib/gitlab/markdown.rb4
-rw-r--r--lib/gitlab/markdown/autolink_filter.rb95
-rw-r--r--lib/redcarpet/render/gitlab_html.rb18
3 files changed, 103 insertions, 14 deletions
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index ffb7e16aed2..beb97bbdf41 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -30,6 +30,7 @@ module Gitlab
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
module Markdown
# Provide autoload paths for filters to prevent a circular dependency error
+ autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter'
autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter'
autoload :CommitReferenceFilter, 'gitlab/markdown/commit_reference_filter'
autoload :EmojiFilter, 'gitlab/markdown/emoji_filter'
@@ -122,6 +123,7 @@ module Gitlab
Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter,
+ Gitlab::Markdown::AutolinkFilter,
Gitlab::Markdown::UserReferenceFilter,
Gitlab::Markdown::IssueReferenceFilter,
@@ -130,7 +132,7 @@ module Gitlab
Gitlab::Markdown::SnippetReferenceFilter,
Gitlab::Markdown::CommitRangeReferenceFilter,
Gitlab::Markdown::CommitReferenceFilter,
- Gitlab::Markdown::LabelReferenceFilter,
+ Gitlab::Markdown::LabelReferenceFilter
]
end
diff --git a/lib/gitlab/markdown/autolink_filter.rb b/lib/gitlab/markdown/autolink_filter.rb
new file mode 100644
index 00000000000..97cfc1893ad
--- /dev/null
+++ b/lib/gitlab/markdown/autolink_filter.rb
@@ -0,0 +1,95 @@
+require 'html/pipeline/filter'
+require 'uri'
+
+module Gitlab
+ module Markdown
+ # HTML Filter for auto-linking URLs in HTML.
+ #
+ # Based on HTML::Pipeline::AutolinkFilter
+ #
+ # Context options:
+ # :autolink - Boolean, skips all processing done by this filter when false
+ # :link_attr - Hash of attributes for the generated links
+ #
+ class AutolinkFilter < HTML::Pipeline::Filter
+ include ActionView::Helpers::TagHelper
+
+ # Pattern to match text that should be autolinked.
+ #
+ # A URI scheme begins with a letter and may contain letters, numbers,
+ # plus, period and hyphen. Schemes are case-insensitive but we're being
+ # picky here and allowing only lowercase for autolinks.
+ #
+ # See http://en.wikipedia.org/wiki/URI_scheme
+ #
+ # The negative lookbehind ensures that users can paste a URL followed by a
+ # period or comma for punctuation without those characters being included
+ # in the generated link.
+ SCHEME_PATTERN = %r{([a-z][a-z0-9_\+\.\-]+:\/\/[^\s]+)(?<!,|\.)}
+
+ # Text matching SCHEME_PATTERN inside these elements will not be linked
+ IGNORE_PARENTS = %w(a code kbd pre script style).to_set
+
+ def call
+ return doc if context[:autolink] == false
+
+ rinku_parse
+ text_parse
+ end
+
+ private
+
+ # Run the text through Rinku as a first pass
+ #
+ # This will quickly autolink http(s) and ftp links.
+ #
+ # `@doc` will be re-parsed with the HTML String from Rinku.
+ def rinku_parse
+ # Convert the options from a Hash to a String that Rinku expects
+ options = tag_options(link_options)
+
+ # NOTE: We don't parse email links because it will erroneously match
+ # external Commit and CommitRange references.
+ rinku = Rinku.auto_link(html, :urls, options, IGNORE_PARENTS.to_a)
+
+ # Rinku returns a String, so parse it back to a Nokogiri::XML::Document
+ # for further processing.
+ @doc = parse_html(rinku)
+ end
+
+ # Autolinks any text matching SCHEME_PATTERN that Rinku didn't already
+ # replace
+ def text_parse
+ search_text_nodes(doc).each do |node|
+ content = node.to_html
+
+ next if has_ancestor?(node, IGNORE_PARENTS)
+ next unless content.match(SCHEME_PATTERN)
+
+ # If Rinku didn't link this, there's probably a good reason, so we'll
+ # skip it too
+ next if content.start_with?(*%w(http https ftp))
+
+ html = autolink_filter(content)
+
+ next if html == content
+
+ node.replace(html)
+ end
+
+ doc
+ end
+
+ def autolink_filter(text)
+ text.gsub(SCHEME_PATTERN) do |match|
+ options = link_options.merge(href: match)
+ content_tag(:a, match, options)
+ end
+ end
+
+ def link_options
+ @link_options ||= context[:link_attr] || {}
+ end
+ end
+ end
+end
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index dc5fbe3c8e1..321be9202cc 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -8,13 +8,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
@color_scheme = color_scheme
@project = @template.instance_variable_get("@project")
@options = options.dup
- super options
- end
- def preprocess(full_document)
- # Redcarpet doesn't allow SMB links when `safe_links_only` is enabled.
- # FTP links are allowed, so we trick Redcarpet.
- full_document.gsub("smb://", "ftp://smb:")
+ super(options)
end
# If project has issue number 39, apostrophe will be linked in
@@ -25,6 +20,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
# This only influences regular text, code blocks are untouched.
def normal_text(text)
return text unless text.present?
+
text.gsub("'", "&rsquo;")
end
@@ -37,7 +33,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
# so we assume you're not using leading spaces that aren't tabs,
# and just replace them here.
if lexer.tag == 'make'
- code.gsub! /^ /, "\t"
+ code.gsub!(/^ /, "\t")
end
formatter = Rugments::Formatters::HTML.new(
@@ -46,17 +42,13 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
formatter.format(lexer.lex(code))
end
- def link(link, title, content)
- h.link_to_gfm(content, link, title: title)
- end
-
def postprocess(full_document)
- full_document.gsub!("ftp://smb:", "smb://")
-
full_document.gsub!("&rsquo;", "'")
+
unless @template.instance_variable_get("@project_wiki") || @project.nil?
full_document = h.create_relative_links(full_document)
end
+
h.gfm_with_options(full_document, @options)
end
end