Index: app/models/mail_handler.rb =================================================================== --- app/models/mail_handler.rb (revision 22112) +++ app/models/mail_handler.rb (working copy) @@ -207,7 +207,6 @@ if issue.subject.blank? issue.subject = "(#{ll(Setting.default_language, :text_no_subject)})" end - issue.description = cleaned_up_text_body issue.start_date ||= User.current.today if Setting.default_issue_start_date_to_creation_date? if handler_options[:issue][:is_private] == '1' issue.is_private = true @@ -215,8 +214,17 @@ # add To and Cc as watchers before saving so the watchers can reply to Redmine add_watchers(issue) - issue.save! - add_attachments(issue) + + if Setting.mail_handler_avoid_attachment_duplication? || Setting.mail_handler_keep_layout? + atts = process_attachments(issue) + issue.description = Setting.mail_handler_keep_layout? ? markup_images(cleaned_up_text_body, atts, Setting.text_formatting) : cleaned_up_text_body + issue.save! + create_pending_attachments(issue, atts) + else + issue.description = cleaned_up_text_body + issue.save! + add_attachments(issue) + end logger&.info "MailHandler: issue ##{issue.id} created by #{user}" issue end @@ -250,12 +258,20 @@ end issue.safe_attributes = issue_attributes_from_keywords(issue) issue.safe_attributes = {'custom_field_values' => custom_field_values_from_keywords(issue)} - journal.notes = cleaned_up_text_body # add To and Cc as watchers before saving so the watchers can reply to Redmine add_watchers(issue) - issue.save! - add_attachments(issue) + + if Setting.mail_handler_avoid_attachment_duplication? || Setting.mail_handler_keep_layout? + atts = process_attachments(issue) + journal.notes = Setting.mail_handler_keep_layout? ? markup_images(cleaned_up_text_body, atts, Setting.text_formatting) : cleaned_up_text_body + issue.save! + create_pending_attachments(issue, atts) + else + journal.notes = cleaned_up_text_body + issue.save! + add_attachments(issue) + end logger&.info "MailHandler: issue ##{issue.id} updated by #{user}" journal end @@ -320,6 +336,159 @@ end end + def process_attachments(obj) + atts = [] + + if !email.attachments || !email.attachments.any? + return atts + end + + existing_filenames = [] + + if Setting.mail_handler_keep_layout? + existing_filenames = Attachment.where( + :container_id => obj.id, + :container_type => obj.class.name, + ).distinct.pluck(:filename) + end + + email.attachments.each do |attachment| + filename = attachment.filename + filesize = attachment.body.decoded.size + content_type = attachment.mime_type + digest = Digest::SHA256.hexdigest(attachment.body.decoded) + att = { + :original_filename => filename, + :filesize => filesize, + :content_type => content_type, + :digest => digest, + :available => true, + :markedup => false, + :content_id => attachment.content_id ? attachment.content_id.delete('<>') : '' + } + + if not accept_attachment?(attachment) or + not (filesize > 0) + att[:available] = false + atts << att + next + end + + if Setting.mail_handler_avoid_attachment_duplication? + dup = duplicated?(obj, filesize, content_type, digest) + if dup + att[:filename] = dup.filename + atts << att + next + end + end + + att[:file] = attachment.body.decoded # this will indicate this att must be created after obj.save! + att[:filename] = Setting.mail_handler_keep_layout? ? self.class.uniquefy_filename(existing_filenames, filename) : filename + + atts << att + end + + atts + end + + def create_pending_attachments(obj, atts) + atts.each do |att| + next unless att.key?(:file) + obj.attachments << Attachment.create(:container => obj, + :file => att[:file], + :filename => att[:filename], + :author => user, + :content_type => att[:content_type]) + end + end + + def self.uniquefy_filename(filenames, filename) + if filenames.include?(filename) + m = filename.match(/^(.*)(\.[^.]+)$/) + if m + base_name, extension = m.captures + else + base_name, extension = filename, "" + end + + count = 1 + new_filename = "#{base_name}-#{count}#{extension}" + while filenames.include?(new_filename) + count += 1 + new_filename = "#{base_name}-#{count}#{extension}" + end + + # append new_filename and return it + filenames << new_filename + new_filename + else + filenames << filename + filename + end + end + + def self.gen_image_markup(att, default, text_formatting) + if ["common_mark", "markdown"].include? text_formatting + filename = Addressable::URI.encode(att[:filename]) + "![#{att[:original_filename]}](#{filename})" + elsif text_formatting == "textile" + filename = URI.encode_www_form_component(att[:filename]).gsub("+", "%20") + "!#{filename}!" + else + default + end + end + + # converts image references to textile/markdown/common_mark formatting. + def markup_images(text_body, atts, text_formatting) + re = /\[image:\s+([^\]]+)\]/ + temp = text_body.gsub(re) { |match| + src = $1 + if src.start_with?('cid:') + # text/html: search by Content-ID + cid = src[4..-1] + att = atts.find{|att| att[:content_id] == cid} + else + # text/plain: get images by position in array + # this will permit to even get the correct image in case they have the same name (could happen in case the user does copy/paste of images in gmail) + # as we will follow the order of attachment + att = atts.find{|att| att[:content_type].start_with?('image/') && !att[:markedup]} + end + if att + if att[:available] + att[:markedup] = true + self.class.gen_image_markup(att, match, text_formatting) + else + match + end + else + match + end + } + + # append remaining images (images not referenced in the text_body) to the end of the text_body + temp = temp + "\n" + atts.each do |att| + if att[:content_type].starts_with?("image/") && att[:available] && !att[:markedup] + temp = temp + "\n" + self.class.gen_image_markup(att, "", text_formatting) + end + end + + temp + end + + # Returns attachment from db if already exists. + def duplicated?(obj, filesize, content_type, digest) + Attachment.where( + :container_id => obj.id, + :container_type => obj.class.name, + :filesize => filesize, + :content_type => content_type, + :digest => digest) + .first + end + # Returns false if the +attachment+ of the incoming email should be ignored def accept_attachment?(attachment) @excluded ||= Setting.mail_handler_excluded_filenames.to_s.split(',').map(&:strip).reject(&:blank?) @@ -550,6 +719,23 @@ class << self # Converts a HTML email body to text def html_body_to_text(html) + if Setting.mail_handler_keep_layout? + # converts to [image: FILENAME] + re = /()/ + html = html.gsub(re) do + doc = Nokogiri::XML($1) + return $1 unless (doc && doc.root && doc.root.key?('src') && doc.root.key?('alt')) + src = doc.root['src'] + filename = doc.root['alt'] + if src && src.start_with?('cid:') + # the cid: indicates this references an attached document. + "[image: #{src}]" + else + $1 + end + end + end + Redmine::WikiFormatting.html_parser.to_text(html) end Index: config/settings.yml =================================================================== --- config/settings.yml (revision 22112) +++ config/settings.yml (working copy) @@ -214,6 +214,10 @@ security_notifications: 1 mail_handler_preferred_body_part: default: plain +mail_handler_avoid_attachment_duplication: + default: 0 +mail_handler_keep_layout: + default: 0 issue_list_default_columns: serialized: true default: Index: test/fixtures/mail_handler/gmail_with_images_and_pdf.eml =================================================================== --- test/fixtures/mail_handler/gmail_with_images_and_pdf.eml (nonexistent) +++ test/fixtures/mail_handler/gmail_with_images_and_pdf.eml (working copy) @@ -0,0 +1,554 @@ +MIME-Version: 1.0 +Date: Sun, 19 Feb 2023 16:18:59 +0900 +Message-ID: +Subject: test +From: John Smith +To: redmine@somenet.foo +Content-Type: multipart/mixed; boundary="00000000000044eaed05f5085c0a" + +--00000000000044eaed05f5085c0a +Content-Type: multipart/related; boundary="00000000000044eaec05f5085c09" + +--00000000000044eaec05f5085c09 +Content-Type: multipart/alternative; boundary="00000000000044eae905f5085c08" + +--00000000000044eae905f5085c08 +Content-Type: text/plain; charset="UTF-8" + +This is image 1: +[image: image.png] + +This is image 2: +[image: image.png] + +--00000000000044eae905f5085c08 +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +
This is image 1:
3D"=

This = +is image 2:
3D"image.png"



+ +--00000000000044eae905f5085c08-- +--00000000000044eaec05f5085c09 +Content-Type: image/png; name="image.png" +Content-Disposition: attachment; filename="image.png" +Content-Transfer-Encoding: base64 +X-Attachment-Id: ii_leb23ykd0 +Content-ID: + +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 +TkSuQmCC +--00000000000044eaec05f5085c09 +Content-Type: image/png; name="image.png" +Content-Disposition: attachment; filename="image.png" +Content-Transfer-Encoding: base64 +X-Attachment-Id: ii_leb24cuc1 +Content-ID: + +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 +--00000000000044eaec05f5085c09-- +--00000000000044eaed05f5085c0a +Content-Type: application/pdf; name="minimal.pdf" +Content-Disposition: attachment; filename="minimal.pdf" +Content-Transfer-Encoding: base64 +X-Attachment-Id: f_leb255r03 +Content-ID: + +JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2Vz +IDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMg +WzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+Pgpl +bmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAg +ICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAg +ICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAg +ICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+Pgog +ICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoK +CjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAg +IDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYK +MCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAw +MDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAg +PDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg== +--00000000000044eaed05f5085c0a +Content-Type: image/gif; name="image3.gif" +Content-Disposition: attachment; filename="image3.gif" +Content-Transfer-Encoding: base64 +X-Attachment-Id: f_leb255qn2 +Content-ID: + +R0lGODlhZgBmAPYAAAAVMgAbNwAeOgAjPQAoPwAmQAIrQgstRgctSAssSAQwRQUxSA0ySxM2TRg2 +TxU5TxM2UBg3UhY5UBs9VCE+ViJAViJDWypGXSZJXCpJXSxNYjFNYzRSZzhSZjdUaDlWaj9cbUJc +bkVfcUdhcktkdU1leU9pe1Fnd1BneFRsfFpvf71ebr9hbcpQZFpvgFxzgmJ3hmN5h2Z7iGl7i22B +jnSGj3SFknaJlnmKlX2MmX6Qm4SUnY2bpJKdpZCgp5SiqZmlrJ2pr5+psaOrr6SutKeztauytqaz +uau1ua+5vrG3urO5vbi8vsyLlseQmcWmrc2lrc2stMewt8q1u8W4vbW9wbm/wcu7wLzBw8PFx8nC +x8bIx8XHysfJysrMzM/Qz9DQz8bN0M3P0dDP0c7Q0dLT0tjX1tfY1djY1gAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5 +BAAAAAAALAAAAABmAGYAAAf+gFxdXVxXYYdhV4aIV4Ncj4+OYYJehIOXk1pcYYSCm4WHkJCJk5OE +Xl6mooKOopNej5OjsYucloe1jIeOXba8rJa9l5uXnbNcsJLIk1ewsMCyvVvAw1xavr2rhoaiXtzb +iInF4+StWrBawcGv15DJnYiDYVvDl87I5ePAsbKlsZqHsJAKgyWfwVDL7GVrhcyZl3awSF2iVw5Z +JIPkEIbRUmsRtWiDKBZ7hWzLtC333pFTaaySOpddRJLDhw/jIG6pFJH69JHaFk5eTHZB9cgkKljT +iIYslnRQpYYwJ1J0KXMcKoNRJYUKFwmb03yPUE3jgtIipJVWzVLLh9JmuUr+VY0x2wmSqcygasWS +dXjRXtW8xWrmY2mQXtxsiJJ5LVc1KNOhTYfGnIixbNLIktEG7lssa+YuOjlbdRkUpsx5p8gu7cyU +6tCwsEXKpkjPsUXPMWtThHYId8vHlY22Jnf4dum1Tl1PdBbStO9eVzQRS0sYq27kmvW9JtuWOFiL +k8fFTdULV7CTSUtiK+3d7eTDdqVSHsyq8ertNwNaLWvxMHzG7q10mkJssSfeTO/UQlxRNQk1mWPz +7Qchad01d59bFRJi0l1vwbaJRBbyNZVNpTkY3nxVpQhTULJBeFdZwPmVHik6IbJXgBG2R+ByFT2T +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 +--00000000000044eaed05f5085c0a-- Index: test/unit/mail_handler_test.rb =================================================================== --- test/unit/mail_handler_test.rb (revision 22112) +++ test/unit/mail_handler_test.rb (working copy) @@ -1005,6 +1005,24 @@ detail = journal.details.first assert_equal 'attachment', detail.property assert_equal 'Paella.jpg', detail.value + + Setting.mail_handler_avoid_attachment_duplication = 1 + + assert_difference 'Journal.count' do + assert_no_difference 'JournalDetail.count' do + assert_no_difference 'Attachment.count' do + assert_no_difference 'Issue.count' do + journal = submit_email('ticket_with_attachment.eml') do |raw| + raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories' + end + end + end + end + end + + journal = Journal.order('id DESC').first + assert_equal Issue.find(2), journal.journalized + assert_equal 0, journal.details.size end def test_update_issue_should_discard_all_changes_on_validation_failure @@ -1415,6 +1433,184 @@ assert_equal 'application/x-pkcs7-signature', issue.attachments[1].content_type end + def test_uniquefy_filename + filenames = ['a.png', 'a.b.c.png', 'a-1.png', 'name with spaces.gif', 'no_extension'] + + clone = filenames.clone + result = MailHandler.send :uniquefy_filename, clone, 'new_image.png' + assert_equal filenames.clone << 'new_image.png', clone + + clone = filenames.clone + result = MailHandler.send :uniquefy_filename, clone, 'a.png' + assert_equal filenames.clone << 'a-2.png', clone + + clone = filenames.clone + result = MailHandler.send :uniquefy_filename, clone, 'a.b.c.png' + assert_equal filenames.clone << 'a.b.c-1.png', clone + + clone = filenames.clone + result = MailHandler.send :uniquefy_filename, clone, 'name with spaces.gif' + assert_equal filenames.clone << 'name with spaces-1.gif', clone + + clone = filenames.clone + result = MailHandler.send :uniquefy_filename, clone, 'no_extension' + assert_equal filenames.clone << 'no_extension-1', clone + end + + def test_issue_creation_with_keep_layout_disabled + set_tmp_attachments_directory + + issue = + submit_email( + 'gmail_with_images_and_pdf.eml', + :issue => {:project => 'ecookbook'} + ) + assert_kind_of Issue, issue + assert !issue.new_record? + issue.reload + + assert_equal "This is image 1:\r\n[image: image.png]\r\n\r\nThis is image 2:\r\n[image: image.png]" , issue.description + + assert_equal 4, issue.attachments.size + + assert_equal 'image.png', issue.attachments.first.filename + assert_equal 'image/png', issue.attachments.first.content_type + + assert_equal 'image.png', issue.attachments.second.filename + assert_equal 'image/png', issue.attachments.second.content_type + + assert_equal 'minimal.pdf', issue.attachments.third.filename + assert_equal 'application/pdf', issue.attachments.third.content_type + + assert_equal 'image3.gif', issue.attachments.fourth.filename + assert_equal 'image/gif', issue.attachments.fourth.content_type + end + + def test_issue_creation_with_keep_layout_and_text_formatting_common_mark + set_tmp_attachments_directory + + Setting.mail_handler_keep_layout = 1 + Setting.mail_handler_avoid_attachment_duplication = 1 # this should not matter as the issue doesn't exist yet + Setting.text_formatting = 'common_mark' + + issue = + submit_email( + 'gmail_with_images_and_pdf.eml', + :issue => {:project => 'ecookbook'} + ) + assert_kind_of Issue, issue + assert !issue.new_record? + issue.reload + + assert_equal "This is image 1:\r\n![image.png](image.png)\r\n\r\nThis is image 2:\r\n![image.png](image-1.png)\r\n\r\n![image3.gif](image3.gif)" , issue.description + + assert_equal 4, issue.attachments.size + + assert_equal 'image.png', issue.attachments.first.filename + assert_equal 'image/png', issue.attachments.first.content_type + + assert_equal 'image-1.png', issue.attachments.second.filename + assert_equal 'image/png', issue.attachments.second.content_type + + assert_equal 'minimal.pdf', issue.attachments.third.filename + assert_equal 'application/pdf', issue.attachments.third.content_type + + assert_equal 'image3.gif', issue.attachments.fourth.filename + assert_equal 'image/gif', issue.attachments.fourth.content_type + end + + def test_issue_creation_with_keep_layout_and_text_formatting_textile + set_tmp_attachments_directory + + Setting.mail_handler_keep_layout = 1 + Setting.mail_handler_avoid_attachment_duplication = 1 # this should not matter as the issue doesn't exist yet + Setting.text_formatting = 'textile' + + issue = + submit_email( + 'gmail_with_images_and_pdf.eml', + :issue => {:project => 'ecookbook'} + ) + assert_kind_of Issue, issue + assert !issue.new_record? + issue.reload + + assert_equal "This is image 1:\r\n!image.png!\r\n\r\nThis is image 2:\r\n!image-1.png!\r\n\r\n!image3.gif!" , issue.description + + assert_equal 4, issue.attachments.size + + assert_equal 'image.png', issue.attachments.first.filename + assert_equal 'image/png', issue.attachments.first.content_type + + assert_equal 'image-1.png', issue.attachments.second.filename + assert_equal 'image/png', issue.attachments.second.content_type + + assert_equal 'minimal.pdf', issue.attachments.third.filename + assert_equal 'application/pdf', issue.attachments.third.content_type + + assert_equal 'image3.gif', issue.attachments.fourth.filename + assert_equal 'image/gif', issue.attachments.fourth.content_type + end + + def test_update_issue_with_keep_layout_and_avoid_attachment_duplication + Setting.mail_handler_keep_layout = 1 + Setting.mail_handler_avoid_attachment_duplication = 1 + + Setting.text_formatting = 'common_mark' + + assert_difference 'Journal.count' do + assert_difference 'JournalDetail.count', 4 do + assert_difference 'Attachment.count', 4 do + assert_no_difference 'Issue.count' do + journal = submit_email('gmail_with_images_and_pdf.eml') do |raw| + raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories' + end + end + end + end + end + journal = Journal.order('id DESC').first + assert_equal Issue.find(2), journal.journalized + assert_equal 4, journal.details.size + + assert_equal "This is image 1:\n![image.png](image.png)\n\nThis is image 2:\n![image.png](image-1.png)\n\n![image3.gif](image3.gif)" , journal.notes + + first = journal.details.first + assert_equal 'attachment', first.property + assert_equal 'image.png', first.value + + second = journal.details.second + assert_equal 'attachment', second.property + assert_equal 'image-1.png', second.value + + third = journal.details.third + assert_equal 'attachment', third.property + assert_equal 'minimal.pdf', third.value + + fourth = journal.details.fourth + assert_equal 'attachment', fourth.property + assert_equal 'image3.gif', fourth.value + + Setting.text_formatting = 'textile' + + assert_difference 'Journal.count' do + assert_no_difference 'JournalDetail.count' do + assert_no_difference 'Attachment.count' do + assert_no_difference 'Issue.count' do + journal = submit_email('gmail_with_images_and_pdf.eml') do |raw| + raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories' + end + end + end + end + end + journal = Journal.order('id DESC').first + assert_equal Issue.find(2), journal.journalized + assert_equal 0, journal.details.size + + assert_equal "This is image 1:\n!image.png!\n\nThis is image 2:\n!image-1.png!\n\n!image3.gif!" , journal.notes + end + private def submit_email(filename, options={})