From 3b690891f36975a35923f14388901f4f2a2c3ed9 Mon Sep 17 00:00:00 2001 From: Robert Speicher <rspeicher@gmail.com> Date: Thu, 3 Sep 2015 17:35:50 -0400 Subject: [PATCH] Basic support for an Atom-specific rendering pipeline --- app/helpers/gitlab_markdown_helper.rb | 10 +- app/views/events/_event_issue.atom.haml | 2 +- .../events/_event_merge_request.atom.haml | 2 +- app/views/events/_event_note.atom.haml | 2 +- app/views/events/_event_push.atom.haml | 2 +- lib/gitlab/markdown.rb | 111 ++++++++++-------- 6 files changed, 70 insertions(+), 59 deletions(-) diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 803578f1911..0d175e1ea18 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -47,15 +47,16 @@ module GitlabMarkdownHelper def markdown(text, context = {}) return unless text.present? - context.merge!( + context.reverse_merge!( path: @path, + pipeline: :default, project: @project, project_wiki: @project_wiki, ref: @ref ) html = Gitlab::Markdown.render(text, context) - Gitlab::Markdown.post_process(html, current_user) + Gitlab::Markdown.post_process(html, pipeline: context[:pipeline], user: current_user) end # TODO (rspeicher): Remove all usages of this helper and just call `markdown` @@ -63,15 +64,16 @@ module GitlabMarkdownHelper def gfm(text, options = {}) return unless text.present? - options.merge!( + options.reverse_merge!( path: @path, + pipeline: :default, project: @project, project_wiki: @project_wiki, ref: @ref ) html = Gitlab::Markdown.gfm(text, options) - Gitlab::Markdown.post_process(html, current_user) + Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], user: current_user) end def asciidoc(text) diff --git a/app/views/events/_event_issue.atom.haml b/app/views/events/_event_issue.atom.haml index 4e8d70e4e9d..fad65310021 100644 --- a/app/views/events/_event_issue.atom.haml +++ b/app/views/events/_event_issue.atom.haml @@ -1,2 +1,2 @@ %div{xmlns: "http://www.w3.org/1999/xhtml"} - = markdown(issue.description, xhtml: true, reference_only_path: false, project: issue.project) + = markdown(issue.description, pipeline: :atom, project: issue.project) diff --git a/app/views/events/_event_merge_request.atom.haml b/app/views/events/_event_merge_request.atom.haml index db2b3550c49..19bdc7b9ca5 100644 --- a/app/views/events/_event_merge_request.atom.haml +++ b/app/views/events/_event_merge_request.atom.haml @@ -1,2 +1,2 @@ %div{xmlns: "http://www.w3.org/1999/xhtml"} - = markdown(merge_request.description, xhtml: true, reference_only_path: false, project: merge_request.project) + = markdown(merge_request.description, pipeline: :atom, project: merge_request.project) diff --git a/app/views/events/_event_note.atom.haml b/app/views/events/_event_note.atom.haml index cfbfba50202..b730ebbd5f9 100644 --- a/app/views/events/_event_note.atom.haml +++ b/app/views/events/_event_note.atom.haml @@ -1,2 +1,2 @@ %div{xmlns: "http://www.w3.org/1999/xhtml"} - = markdown(note.note, xhtml: true, reference_only_path: false, project: note.project) + = markdown(note.note, pipeline: :atom, project: note.project) diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml index 3625cb49d8b..b271b9daff1 100644 --- a/app/views/events/_event_push.atom.haml +++ b/app/views/events/_event_push.atom.haml @@ -6,7 +6,7 @@ %i at = commit[:timestamp].to_time.to_s(:short) - %blockquote= markdown(escape_once(commit[:message]), xhtml: true, reference_only_path: false, project: event.project) + %blockquote= markdown(escape_once(commit[:message]), pipeline: :atom, project: event.project) - if event.commits_count > 15 %p %i diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index dbb8da3f0ad..476c736a11a 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -7,6 +7,14 @@ module Gitlab module Markdown # Convert a Markdown String into an HTML-safe String of HTML # + # Note that while the returned HTML will have been sanitized of dangerous + # HTML, it may post a risk of information leakage if it's not also passed + # through `post_process`. + # + # Also note that the returned String is always HTML, not XHTML. Views + # requiring XHTML, such as Atom feeds, need to call `post_process` on the + # result, providing the appropriate `pipeline` option. + # # markdown - Markdown String # context - Hash of context options passed to our HTML Pipeline # @@ -38,15 +46,19 @@ module Gitlab # permission to make (`RedactorFilter`). # # html - String to process - # for_user - User state + # options - Hash of options to customize output + # :pipeline - Symbol pipeline type + # :user - User object # # Returns an HTML-safe String - def self.post_process(html, for_user) - result = post_processor.call(html, current_user: for_user) - - result[:output]. - to_html. - html_safe + def self.post_process(html, options) + doc = post_processor.to_document(html, current_user: options[:user]) + + if options[:pipeline] == :atom + doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML) + else + doc.to_html + end.html_safe end # Provide autoload paths for filters to prevent a circular dependency error @@ -68,26 +80,20 @@ module Gitlab autoload :TaskListFilter, 'gitlab/markdown/task_list_filter' autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter' - # Public: Parse the provided text with GitLab-Flavored Markdown + # Public: Parse the provided HTML with GitLab-Flavored Markdown # - # text - the source text - # options - A Hash of options used to customize output (default: {}): - # :xhtml - output XHTML instead of HTML - # :reference_only_path - Use relative path for reference links - def self.gfm(text, options = {}) - return text if text.nil? - - # Duplicate the string so we don't alter the original, then call to_str - # to cast it back to a String instead of a SafeBuffer. This is required - # for gsub calls to work as we need them to. - text = text.dup.to_str - - options.reverse_merge!( - xhtml: false, - reference_only_path: true, - project: options[:project], - current_user: options[:current_user] - ) + # html - HTML String + # options - A Hash of options used to customize output (default: {}) + # :no_header_anchors - Disable header anchors in TableOfContentsFilter + # :path - Current path String + # :pipeline - Symbol pipeline type + # :project - Current Project object + # :project_wiki - Current ProjectWiki object + # :ref - Current ref String + # + # Returns an HTML-safe String + def self.gfm(html, options = {}) + return '' unless html.present? @pipeline ||= HTML::Pipeline.new(filters) @@ -96,47 +102,39 @@ module Gitlab pipeline: options[:pipeline], # EmojiFilter - asset_root: Gitlab.config.gitlab.url, asset_host: Gitlab::Application.config.asset_host, - - # TableOfContentsFilter - no_header_anchors: options[:no_header_anchors], + asset_root: Gitlab.config.gitlab.url, # ReferenceFilter - only_path: options[:reference_only_path], - project: options[:project], + only_path: only_path_pipeline?(options[:pipeline]), + project: options[:project], # RelativeLinkFilter + project_wiki: options[:project_wiki], ref: options[:ref], requested_path: options[:path], - project_wiki: options[:project_wiki] - } - - result = @pipeline.call(text, context) - save_options = 0 - if options[:xhtml] - save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML - end - - text = result[:output].to_html(save_with: save_options) + # TableOfContentsFilter + no_header_anchors: options[:no_header_anchors] + } - text.html_safe + @pipeline.to_html(html, context).html_safe end private - def self.renderer - @markdown ||= begin - renderer = Redcarpet::Render::HTML.new - Redcarpet::Markdown.new(renderer, redcarpet_options) + # Check if a pipeline enables the `only_path` context option + # + # Returns Boolean + def self.only_path_pipeline?(pipeline) + case pipeline + when :atom, :email + false + else + true end end - def self.post_processor - @post_processor ||= HTML::Pipeline.new([Gitlab::Markdown::RedactorFilter]) - end - def self.redcarpet_options # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use @redcarpet_options ||= { @@ -151,6 +149,17 @@ module Gitlab }.freeze end + def self.renderer + @markdown ||= begin + renderer = Redcarpet::Render::HTML.new + Redcarpet::Markdown.new(renderer, redcarpet_options) + end + end + + def self.post_processor + @post_processor ||= HTML::Pipeline.new([Gitlab::Markdown::RedactorFilter]) + end + # Filters used in our pipeline # # SanitizationFilter should come first so that all generated reference HTML -- GitLab