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