FEATURE: Parse images in email signatures (#10137)

FEATURE: Parse images in email signatures (#10137)

  • FEATURE: Parse images in email signatures

  • DEV: Fix tests

  • Code review

diff --git a/app/services/inline_uploads.rb b/app/services/inline_uploads.rb
index 0facc32..a63f0af 100644
--- a/app/services/inline_uploads.rb
+++ b/app/services/inline_uploads.rb
@@ -197,19 +197,23 @@ class InlineUploads
     end
   end
 
-  def self.match_img(markdown, external_src: false)
+  def self.match_img(markdown, external_src: false, uploads: nil)
     markdown.scan(/(<(?!img)[^<>]+\/?>)?(\s*)(<img [^>\n]+>)/i) do |match|
       node = Nokogiri::HTML5::fragment(match[2].strip).children[0]
       src =  node.attributes["src"]&.value
 
       if src && (matched_uploads(src).present? || external_src)
-        text = node.attributes["alt"]&.value
-        width = node.attributes["width"]&.value.to_i
-        height = node.attributes["height"]&.value.to_i
+        upload = uploads&.[](src)
+
+        text = upload&.original_filename || node.attributes["alt"]&.value
+        width = (node.attributes["width"]&.value || upload&.width).to_i
+        height = (node.attributes["height"]&.value || upload&.height).to_i
         title = node.attributes["title"]&.value
         text = "#{text}|#{width}x#{height}" if width > 0 && height > 0
+        url = upload&.short_url || PLACEHOLDER
+
         spaces_before = match[1].present? ? match[1][/ +$/].size : 0
-        replacement = +"#{" " * spaces_before}![#{text}](#{PLACEHOLDER}#{title.present? ? " \"#{title}\"" : ""})"
+        replacement = +"#{" " * spaces_before}![#{text}](#{url}#{title.present? ? " \"#{title}\"" : ""})"
 
         yield(match[2], src, replacement, $~.offset(0)[0]) if block_given?
       end
diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb
index fad5e42..9fab909 100644
--- a/lib/email/receiver.rb
+++ b/lib/email/receiver.rb
@@ -384,7 +384,8 @@ module Email
 
     def to_markdown(html, elided_html)
       markdown = HtmlToMarkdown.new(html, keep_img_tags: true, keep_cid_imgs: true).to_markdown
-      [EmailReplyTrimmer.trim(markdown), HtmlToMarkdown.new(elided_html).to_markdown]
+      elided_markdown = HtmlToMarkdown.new(elided_html, keep_img_tags: true, keep_cid_imgs: true).to_markdown
+      [EmailReplyTrimmer.trim(markdown), elided_markdown]
     end
 
     HTML_EXTRACTERS ||= [
@@ -1032,6 +1033,7 @@ module Email
     end
 
     def create_post_with_attachments(options = {})
+      add_elided_to_raw!(options)
       options[:raw] = add_attachments(options[:raw], options[:user], options)
       create_post(options)
     end
@@ -1054,9 +1056,9 @@ module Email
               if raw[attachment.url]
                 raw.sub!(attachment.url, upload.url)
 
-                InlineUploads.match_img(raw) do |match, src, replacement, _|
+                InlineUploads.match_img(raw, uploads: { upload.url => upload }) do |match, src, replacement, _|
                   if src == upload.url
-                    raw = raw.sub(match, UploadMarkdown.new(upload).image_markdown)
+                    raw = raw.sub(match, replacement)
                   end
                 end
               elsif raw[/\[image:.*?\d+[^\]]*\]/i]
@@ -1100,20 +1102,25 @@ module Email
       Email::Sender.new(client_message, :email_reject_attachment).send
     end
 
-    def create_post(options = {})
-      options[:via_email] = true
-      options[:raw_email] = @raw_email
-
-      options[:created_at] ||= @mail.date
-      options[:created_at] = DateTime.now if options[:created_at] > DateTime.now
-
+    def add_elided_to_raw!(options)
       is_private_message = options[:archetype] == Archetype.private_message ||
                            options[:topic].try(:private_message?)
 
       # only add elided part in messages
       if options[:elided].present? && (SiteSetting.always_show_trimmed_content || is_private_message)
         options[:raw] << Email::Receiver.elided_html(options[:elided])
+        options[:elided] = ""
       end
+    end
+
+    def create_post(options = {})
+      options[:via_email] = true
+      options[:raw_email] = @raw_email
+
+      options[:created_at] ||= @mail.date
+      options[:created_at] = DateTime.now if options[:created_at] > DateTime.now
+
+      add_elided_to_raw!(options)
 
       if sent_to_mailinglist_mirror?
         options[:skip_validations] = true
diff --git a/spec/components/email/receiver_spec.rb b/spec/components/email/receiver_spec.rb
index 9a601c0..553ce77 100644
--- a/spec/components/email/receiver_spec.rb
+++ b/spec/components/email/receiver_spec.rb
@@ -572,6 +572,27 @@ describe Email::Receiver do
       MD
     end
 
+    it "supports attached images in signature" do
+      SiteSetting.incoming_email_prefer_html = true
+      SiteSetting.always_show_trimmed_content = true
+
+      expect { process(:body_with_image) }.to change { topic.posts.count }
+
+      post = topic.posts.last
+      upload = post.uploads.last
+
+      expect(post.raw).to eq(<<~MD.chomp)
+      This is a **GMAIL** reply ;)
+
+      <details class='elided'>
+      <summary title='Show trimmed content'>&#183;&#183;&#183;</summary>
+
+      ![logo.png|300x200](upload://qUm0DGR49PAZshIi7HxMd3cAlzn.png)
+
+      </details>
+      MD
+    end
+
     it "supports attachments" do
       SiteSetting.authorized_extensions = "txt|jpg"
       expect { process(:attached_txt_file) }.to change { topic.posts.count }
diff --git a/spec/fixtures/emails/body_with_image.eml b/spec/fixtures/emails/body_with_image.eml
new file mode 100644
index 0000000..c3310a0
--- /dev/null
+++ b/spec/fixtures/emails/body_with_image.eml
@@ -0,0 +1,87 @@
+Return-Path: <discourse@bar.com>
+From: Foo Bar <discourse@bar.com>
+To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com
+Date: Fri, 15 Jan 2016 00:12:43 +0100
+Message-ID: <6@foo.bar.mail>
+Mime-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="--==_mimepart_56990c8d3f66c_7cb53ffbb98602004746e";
+ charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+
+----==_mimepart_56990c8d3f66c_7cb53ffbb98602004746e
+Content-Type: text/html; charset=UTF-8
+
+<div>
+  <div dir="auto">
+    <p>This is a <b>GMAIL</b> reply ;)</p>
+  </div>
+  <div class="gmail_signature">
+    <img src="cid:56990c92d6c64_7cb53ffbb986020047616@foo.bar.mail" width="300" height="200">
+  </div>
+</div>
+
+----==_mimepart_56990c8d3f66c_7cb53ffbb98602004746e
+Content-Type: image/png;
+ charset=UTF-8;
+ filename=logo.png
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename=logo.png
+Content-ID: <56990c92d6c64_7cb53ffbb986020047616@foo.bar.mail>
+
+iVBORw0KGgoAAAANSUhEUgAAAPQAAABCCAMAAABXYgukAAABhlBMVEUAAAAj
+HyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAjHyAj
+HyAAqVAAru/lGyTxXCL/+a5UHiHZGyTqNyPtRCM7HyHwWCLrPCOEHSL95Z31
+g0W1HCMgs1wAqnghKC0Aq5YJirsAqm6oHCMPb5QArccUXnsSZ4hAvWj80ouP
+1oXoKyR4HSL0eTz4q2j+76XsQCOf24vpLyNsHiJgHiEYTGHyZiucHSPzcDTf
+76ILga7qMyMwuGIArdEaQ1T7yIJwzHn2l1f6vnovHyDuTCPNHCQfMTr3oV/w
+VCL5tHEHk8gArKDnJyTvUCIAruUWVW4ArLMQrlYNeKEEnNVQwm0cOkcAq4KA
+0X/mHyQAqVoArKkAq4wCpeIArdvnIyT1jU4AqmQUXV3v9KikRSHP6pwwIyBH
+HiHKUSLxaDTtSCMCpLpgx3PaHyTtSSy/5Zetw3b83JSVSiT0i1egMSI+qVa2
+JCMrbVbiX0Ygs2YArL2LkGApkWQLf2kSZmpFMZD0AAAAEHRSTlMAEFCAv8+v
+QCBw72CP358w5xEcGAAABxJJREFUeF7lmuW/uzoMh1eKrUApbMfd9efu7u5u
+193d739+gbHQBtg49x3b99U5H7asD0mTNNAoE9GobohUukmtxqDLbhoCi5nO
+ICNruigWc+0BRXY80UN8ELEtQ/QWo4OGTFzRX4Y/UMy+ISqpOUi7mYmK4oPD
+LKpLJ4PBzMVOZAwEdVNgje6dmZ+fX5vZ+1mhrwcwto+stTKNnRgdxH3tq0B7
+r6W4d/fEirivHR24HE48Ja47yO+NX22nujlxuDU/ialrXq9NmWUmQd7TIQZN
+TX9zBCczGYCymjVrmoxyIgnrDvLJS5d3jUSa3XXr/fbU+Ayipvi2mTUMbmAe
+T5AvJcCgy3P3fkbUNrTs6QWrNtAUMb+cSJBnR7B27fv4U5TBkQlaG0czKW3H
+zPfiwAYvK9r/OXI1hq5hWzI5FkF/GTHPHRgp1vc/qq5GWcGpC7QnQPMR88E4
+tEdK9ctvMjQjKYAB+bxufcloXKoQM9ah4ENAllxLzFodQ1wBWos29M12e99I
+Ly0sHius1cSCXF6n6J6MHD3dbh+f7Qm9O/hqSaIm9e26IXVPtdu3RnrqVLB4
+GsV37eQo0X0GgrtcZ4Nz5wXIRQB129JjSbm60A/6YrC8gjZ13aQD85UouqOu
+BODKN/VqeF2A0nunxwK3E4fqOm3mU5vfpKZu0qafB/BpZASuVJVNTd4kqW03
+Mq3lEK3YsEs1AtAsK1hJvbrRF/q7IAjXcVOmK+MU2rWqKwi2m/2aR5UUSKgn
+XcEdMgBkHX76ey4kFr/rPqZYtnlGyC0wA9BJ7v6hCvTGbQGyctDEQKMGIFDE
+pEsaU65oVaE5lBBfsuBl0dJkQpZLiqCPj1SCDpfKoOFvnOlsQ2CZBNiQeDXo

[... diff too long, it was truncated ...]

GitHub sha: bd842cd2

This commit appears in #10137 which was approved by eviltrout and ZogStriP. It was merged by SamSaffron.