From c799e4184c3f14e7770afa514621c72ebc408c52 Mon Sep 17 00:00:00 2001 From: Matthew Landauer Date: Mon, 28 Jan 2013 10:55:37 +1100 Subject: Calling TMail::Mail#base64_decode does not modify the mail object so doesn't do anything as used here --- lib/mail_handler/backends/mail_backend.rb | 3 +-- lib/mail_handler/backends/tmail_backend.rb | 10 ++++------ 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb index b75e6ed63..0a12ab3bb 100644 --- a/lib/mail_handler/backends/mail_backend.rb +++ b/lib/mail_handler/backends/mail_backend.rb @@ -8,8 +8,7 @@ module MailHandler 'Mail' end - # Note that the decode flag is not yet used - def mail_from_raw_email(data, decode=true) + def mail_from_raw_email(data) Mail.new(data) end diff --git a/lib/mail_handler/backends/tmail_backend.rb b/lib/mail_handler/backends/tmail_backend.rb index 02124cdb1..1e241f261 100644 --- a/lib/mail_handler/backends/tmail_backend.rb +++ b/lib/mail_handler/backends/tmail_backend.rb @@ -8,14 +8,12 @@ module MailHandler # Turn raw data into a structured TMail::Mail object # Documentation at http://i.loveruby.net/en/projects/tmail/doc/ - def mail_from_raw_email(data, decode=true) + def mail_from_raw_email(data) # Hack round bug in TMail's MIME decoding. # Report of TMail bug: # http://rubyforge.org/tracker/index.php?func=detail&aid=21810&group_id=4512&atid=17370 copy_of_raw_data = data.gsub(/; boundary=\s+"/im,'; boundary="') - mail = TMail::Mail.parse(copy_of_raw_data) - mail.base64_decode if decode - mail + TMail::Mail.parse(copy_of_raw_data) end # Extracts all attachments from the given TNEF file as a TMail::Mail object @@ -105,12 +103,12 @@ module MailHandler if part.content_type == 'message/rfc822' # An email attached as text # e.g. http://www.whatdotheyknow.com/request/64/response/102 - part.rfc822_attachment = mail_from_raw_email(part.body, decode=false) + part.rfc822_attachment = mail_from_raw_email(part.body) elsif part.content_type == 'application/vnd.ms-outlook' || part_filename && AlaveteliFileTypes.filename_to_mimetype(part_filename) == 'application/vnd.ms-outlook' # An email attached as an Outlook file # e.g. http://www.whatdotheyknow.com/request/chinese_names_for_british_politi msg = Mapi::Msg.open(StringIO.new(part.body)) - part.rfc822_attachment = mail_from_raw_email(msg.to_mime.to_s, decode=false) + part.rfc822_attachment = mail_from_raw_email(msg.to_mime.to_s) elsif part.content_type == 'application/ms-tnef' # A set of attachments in a TNEF file part.rfc822_attachment = mail_from_tnef(part.body) -- cgit v1.2.3 From b9a2d996408c1ef703aa78311f8c2a0f6e0d7afc Mon Sep 17 00:00:00 2001 From: Henare Degan Date: Sun, 10 Feb 2013 16:51:30 +1100 Subject: Update this to match the older version of the mail gem we're using --- lib/mail_handler/backends/mail_extensions.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_extensions.rb b/lib/mail_handler/backends/mail_extensions.rb index f756abd1a..83dce5733 100644 --- a/lib/mail_handler/backends/mail_extensions.rb +++ b/lib/mail_handler/backends/mail_extensions.rb @@ -50,8 +50,7 @@ module Mail # A patched version of the parameter hash that handles nil values without throwing # an error. - class ParameterHash < IndifferentHash - + class ParameterHash < HashWithIndifferentAccess def encoded map.sort { |a,b| a.first.to_s <=> b.first.to_s }.map do |key_name, value| # The replacement of this commented out line is the change @@ -64,4 +63,4 @@ module Mail end.join(";\r\n\s") end end -end \ No newline at end of file +end -- cgit v1.2.3 From 60795a7fc49e485858673c9631f346c49f746196 Mon Sep 17 00:00:00 2001 From: Henare Degan Date: Thu, 21 Feb 2013 15:41:57 +1100 Subject: We're using an older version of Mail that returns a different class --- lib/mail_handler/backends/mail_backend.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb index 0a12ab3bb..71c6d1978 100644 --- a/lib/mail_handler/backends/mail_backend.rb +++ b/lib/mail_handler/backends/mail_backend.rb @@ -60,7 +60,7 @@ module MailHandler def get_from_address(mail) first_from = first_from(mail) if first_from - if first_from.is_a?(String) + if first_from.is_a?(ActiveSupport::Multibyte::Chars) return nil else return first_from.address @@ -74,7 +74,7 @@ module MailHandler def get_from_name(mail) first_from = first_from(mail) if first_from - if first_from.is_a?(String) + if first_from.is_a?(ActiveSupport::Multibyte::Chars) return nil else return first_from.display_name ? eval(%Q{"#{first_from.display_name}"}) : nil @@ -319,4 +319,4 @@ module MailHandler end end end -end \ No newline at end of file +end -- cgit v1.2.3 From 3b15df97402411078fd3e4222573e5fefbae9099 Mon Sep 17 00:00:00 2001 From: Henare Degan Date: Thu, 21 Feb 2013 16:00:22 +1100 Subject: Our older version of Mail returns a different class so cast it to a String --- lib/mail_handler/backends/mail_backend.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb index 71c6d1978..f7893a60d 100644 --- a/lib/mail_handler/backends/mail_backend.rb +++ b/lib/mail_handler/backends/mail_backend.rb @@ -85,7 +85,7 @@ module MailHandler end def get_all_addresses(mail) - envelope_to = mail['envelope-to'] ? [mail['envelope-to'].value] : [] + envelope_to = mail['envelope-to'] ? [mail['envelope-to'].value.to_s] : [] ((mail.to || []) + (mail.cc || []) + (envelope_to || [])).uniq -- cgit v1.2.3 From f07444d80961fcd3e7bb9555f5ed4a4e0b65b5db Mon Sep 17 00:00:00 2001 From: Henare Degan Date: Wed, 27 Feb 2013 14:17:16 +1100 Subject: Remove Tmail and use the Mail gem under Ruby 1.8.7 as well --- lib/mail_handler/backends/tmail_backend.rb | 288 -------------------------- lib/mail_handler/backends/tmail_extensions.rb | 138 ------------ 2 files changed, 426 deletions(-) delete mode 100644 lib/mail_handler/backends/tmail_backend.rb delete mode 100644 lib/mail_handler/backends/tmail_extensions.rb (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/tmail_backend.rb b/lib/mail_handler/backends/tmail_backend.rb deleted file mode 100644 index 1e241f261..000000000 --- a/lib/mail_handler/backends/tmail_backend.rb +++ /dev/null @@ -1,288 +0,0 @@ -module MailHandler - module Backends - module TmailBackend - - def backend() - 'TMail' - end - - # Turn raw data into a structured TMail::Mail object - # Documentation at http://i.loveruby.net/en/projects/tmail/doc/ - def mail_from_raw_email(data) - # Hack round bug in TMail's MIME decoding. - # Report of TMail bug: - # http://rubyforge.org/tracker/index.php?func=detail&aid=21810&group_id=4512&atid=17370 - copy_of_raw_data = data.gsub(/; boundary=\s+"/im,'; boundary="') - TMail::Mail.parse(copy_of_raw_data) - end - - # Extracts all attachments from the given TNEF file as a TMail::Mail object - def mail_from_tnef(content) - main = TMail::Mail.new - main.set_content_type 'multipart', 'mixed', { 'boundary' => TMail.new_boundary } - tnef_attachments(content).each do |attachment| - tmail_attachment = TMail::Mail.new - tmail_attachment['content-location'] = attachment[:filename] - tmail_attachment.body = attachment[:content] - main.parts << tmail_attachment - end - main - end - - # Return a copy of the file name for the mail part - def get_part_file_name(mail_part) - part_file_name = TMail::Mail.get_part_file_name(mail_part) - if part_file_name.nil? - return nil - end - part_file_name = part_file_name.dup - return part_file_name - end - - # Get the body of a mail part - def get_part_body(mail_part) - mail_part.body - end - - # Return the first from address if any - def get_from_address(mail) - if mail.from_addrs.nil? || mail.from_addrs.size == 0 - return nil - end - mail.from_addrs[0].spec - end - - # Return the first from name if any - def get_from_name(mail) - mail.from_name_if_present - end - - def get_all_addresses(mail) - ((mail.to || []) + - (mail.cc || []) + - (mail.envelope_to || [])).uniq - end - - def empty_return_path?(mail) - return false if mail['return-path'].nil? - return true if mail['return-path'].addr.to_s == '<>' - return false - end - - def get_auto_submitted(mail) - mail['auto-submitted'] ? mail['auto-submitted'].body : nil - end - - def get_content_type(part) - part.content_type - end - - def get_header_string(header, mail) - mail.header_string(header) - end - - # Number the attachments in depth first tree order, for use in URLs. - # XXX This fills in part.rfc822_attachment and part.url_part_number within - # all the parts of the email (see monkeypatches in lib/mail_handler/tmail_extensions and - # lib/mail_handler/mail_extensions for how these attributes are added). ensure_parts_counted - # must be called before using the attributes. - def ensure_parts_counted(mail) - mail.count_parts_count = 0 - _count_parts_recursive(mail, mail) - # we carry on using these numeric ids for attachments uudecoded from within text parts - mail.count_first_uudecode_count = mail.count_parts_count - end - def _count_parts_recursive(part, mail) - if part.multipart? - part.parts.each do |p| - _count_parts_recursive(p, mail) - end - else - part_filename = get_part_file_name(part) - begin - if part.content_type == 'message/rfc822' - # An email attached as text - # e.g. http://www.whatdotheyknow.com/request/64/response/102 - part.rfc822_attachment = mail_from_raw_email(part.body) - elsif part.content_type == 'application/vnd.ms-outlook' || part_filename && AlaveteliFileTypes.filename_to_mimetype(part_filename) == 'application/vnd.ms-outlook' - # An email attached as an Outlook file - # e.g. http://www.whatdotheyknow.com/request/chinese_names_for_british_politi - msg = Mapi::Msg.open(StringIO.new(part.body)) - part.rfc822_attachment = mail_from_raw_email(msg.to_mime.to_s) - elsif part.content_type == 'application/ms-tnef' - # A set of attachments in a TNEF file - part.rfc822_attachment = mail_from_tnef(part.body) - end - rescue - # If attached mail doesn't parse, treat it as text part - part.rfc822_attachment = nil - else - unless part.rfc822_attachment.nil? - _count_parts_recursive(part.rfc822_attachment, mail) - end - end - if part.rfc822_attachment.nil? - mail.count_parts_count += 1 - part.url_part_number = mail.count_parts_count - end - end - end - - def get_attachment_attributes(mail) - leaves = get_attachment_leaves(mail) - # XXX we have to call ensure_parts_counted after get_attachment_leaves - # which is really messy. - ensure_parts_counted(mail) - attachment_attributes = [] - for leaf in leaves - body = get_part_body(leaf) - # As leaf.body causes MIME decoding which uses lots of RAM, do garbage collection here - # to prevent excess memory use. XXX not really sure if this helps reduce - # peak RAM use overall. Anyway, maybe there is something better to do than this. - GC.start - if leaf.within_rfc822_attachment - within_rfc822_subject = leaf.within_rfc822_attachment.subject - # Test to see if we are in the first part of the attached - # RFC822 message and it is text, if so add headers. - # XXX should probably use hunting algorithm to find main text part, rather than - # just expect it to be first. This will do for now though. - if leaf.within_rfc822_attachment == leaf && leaf.content_type == 'text/plain' - headers = "" - for header in [ 'Date', 'Subject', 'From', 'To', 'Cc' ] - if leaf.within_rfc822_attachment.header.include?(header.downcase) - header_value = leaf.within_rfc822_attachment.header[header.downcase] - if !header_value.blank? - headers = headers + header + ": " + header_value.to_s + "\n" - end - end - end - # XXX call _convert_part_body_to_text here, but need to get charset somehow - # e.g. http://www.whatdotheyknow.com/request/1593/response/3088/attach/4/Freedom%20of%20Information%20request%20-%20car%20oval%20sticker:%20Article%2020,%20Convention%20on%20Road%20Traffic%201949.txt - body = headers + "\n" + body - - # This is quick way of getting all headers, but instead we only add some a) to - # make it more usable, b) as at least one authority accidentally leaked security - # information into a header. - #attachment.body = leaf.within_rfc822_attachment.port.to_s - end - end - attachment_attributes << {:url_part_number => leaf.url_part_number, - :content_type => get_content_type(leaf), - :filename => get_part_file_name(leaf), - :charset => leaf.charset, - :within_rfc822_subject => within_rfc822_subject, - :body => body, - :hexdigest => Digest::MD5.hexdigest(body) } - end - attachment_attributes - end - - # (This risks losing info if the unchosen alternative is the only one to contain - # useful info, but let's worry about that another time) - def get_attachment_leaves(mail) - return _get_attachment_leaves_recursive(mail, mail) - end - def _get_attachment_leaves_recursive(curr_mail, parent_mail, within_rfc822_attachment = nil) - leaves_found = [] - if curr_mail.multipart? - if curr_mail.parts.size == 0 - raise "no parts on multipart mail" - end - - if curr_mail.sub_type == 'alternative' - # Choose best part from alternatives - best_part = nil - # Take the last text/plain one, or else the first one - curr_mail.parts.each do |m| - if not best_part - best_part = m - elsif m.content_type == 'text/plain' - best_part = m - end - end - # Take an HTML one as even higher priority. (They tend - # to render better than text/plain, e.g. don't wrap links here: - # http://www.whatdotheyknow.com/request/amount_and_cost_of_freedom_of_in#incoming-72238 ) - curr_mail.parts.each do |m| - if m.content_type == 'text/html' - best_part = m - end - end - leaves_found += _get_attachment_leaves_recursive(best_part, parent_mail, within_rfc822_attachment) - else - # Add all parts - curr_mail.parts.each do |m| - leaves_found += _get_attachment_leaves_recursive(m, parent_mail, within_rfc822_attachment) - end - end - else - # XXX Yuck. this section alters various content_types. That puts - # it into conflict with ensure_parts_counted which it has to be - # called both before and after. It will fail with cases of - # attachments of attachments etc. - charset = curr_mail.charset # save this, because overwriting content_type also resets charset - # Don't allow nil content_types - if curr_mail.content_type.nil? - curr_mail.content_type = 'application/octet-stream' - end - # PDFs often come with this mime type, fix it up for view code - if curr_mail.content_type == 'application/octet-stream' - part_file_name = get_part_file_name(curr_mail) - part_body = get_part_body(curr_mail) - calc_mime = AlaveteliFileTypes.filename_and_content_to_mimetype(part_file_name, part_body) - if calc_mime - curr_mail.content_type = calc_mime - end - end - - # Use standard content types for Word documents etc. - curr_mail.content_type = normalise_content_type(curr_mail.content_type) - if curr_mail.content_type == 'message/rfc822' - ensure_parts_counted(parent_mail) # fills in rfc822_attachment variable - if curr_mail.rfc822_attachment.nil? - # Attached mail didn't parse, so treat as text - curr_mail.content_type = 'text/plain' - end - end - if curr_mail.content_type == 'application/vnd.ms-outlook' || curr_mail.content_type == 'application/ms-tnef' - ensure_parts_counted(parent_mail) # fills in rfc822_attachment variable - if curr_mail.rfc822_attachment.nil? - # Attached mail didn't parse, so treat as binary - curr_mail.content_type = 'application/octet-stream' - end - end - # If the part is an attachment of email - if curr_mail.content_type == 'message/rfc822' || curr_mail.content_type == 'application/vnd.ms-outlook' || curr_mail.content_type == 'application/ms-tnef' - ensure_parts_counted(parent_mail) # fills in rfc822_attachment variable - leaves_found += _get_attachment_leaves_recursive(curr_mail.rfc822_attachment, parent_mail, curr_mail.rfc822_attachment) - else - # Store leaf - curr_mail.within_rfc822_attachment = within_rfc822_attachment - leaves_found += [curr_mail] - end - # restore original charset - curr_mail.charset = charset - end - return leaves_found - end - - - def address_from_name_and_email(name, email) - if !MySociety::Validate.is_valid_email(email) - raise "invalid email " + email + " passed to address_from_name_and_email" - end - if name.nil? - return TMail::Address.parse(email).to_s - end - # Botch an always quoted RFC address, then parse it - name = name.gsub(/(["\\])/, "\\\\\\1") - TMail::Address.parse('"' + name + '" <' + email + '>').to_s - end - - def address_from_string(string) - TMail::Address.parse(string).address - end - - end - end -end \ No newline at end of file diff --git a/lib/mail_handler/backends/tmail_extensions.rb b/lib/mail_handler/backends/tmail_extensions.rb deleted file mode 100644 index 3576a8eca..000000000 --- a/lib/mail_handler/backends/tmail_extensions.rb +++ /dev/null @@ -1,138 +0,0 @@ -# lib/tmail_extensions.rb: -# Extensions / fixes to TMail. -# -# Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ - -require 'racc/parser' -require 'tmail' -require 'tmail/scanner' -require 'tmail/utils' -require 'tmail/interface' - -# Monkeypatch! - -# These mainly used in app/models/incoming_message.rb -module TMail - class Mail - # Monkeypatch! Adding some extra members to store extra info in. - - attr_accessor :url_part_number - attr_accessor :rfc822_attachment # when a whole email message is attached as text - attr_accessor :within_rfc822_attachment # for parts within a message attached as text (for getting subject mainly) - attr_accessor :count_parts_count - attr_accessor :count_first_uudecode_count - - # Monkeypatch! (check to see if this becomes a standard function in - # TMail::Mail, then use that, whatever it is called) - def Mail.get_part_file_name(part) - file_name = (part['content-location'] && - part['content-location'].body) || - part.sub_header("content-type", "name") || - part.sub_header("content-disposition", "filename") - file_name = file_name.strip if file_name - file_name - end - - # Monkeypatch! Return the name part of from address, or nil if there isn't one - def from_name_if_present - if self.from && self.from_addrs[0].name - return TMail::Unquoter.unquote_and_convert_to(self.from_addrs[0].name, "utf-8") - else - return nil - end - end - - # Monkeypatch! Generalisation of To:, Cc: - def envelope_to(default = nil) - # XXX assumes only one envelope-to, and no parsing needed - val = self.header_string('envelope-to') - return val ? [val,] : [] - end - - # Monkeypatch! - # Bug fix to this function - is for message in humberside-police-odd-mime-type.email - # Which was originally: https://secure.mysociety.org/admin/foi/request/show_raw_email/11209 - # See test in spec/lib/tmail_extensions.rb - def set_content_type( str, sub = nil, param = nil ) - if sub - main, sub = str, sub - else - main, sub = str.split(%r, 2) - raise ArgumentError, "sub type missing: #{str.inspect}" unless sub - end - if h = @header['content-type'] - h.main_type = main - h.sub_type = sub - h.params.clear if !h.params.nil? # XXX this if statement is the fix # XXX disabled until works with test - else - store 'Content-Type', "#{main}/#{sub}" - end - @header['content-type'].params.replace param if param - str - end - # Need to make sure this alias calls the Monkeypatch too - alias content_type= set_content_type - - end - - module TextUtils - # Monkeypatch! Much more aggressive list of characters to cause quoting - # than in normal TMail. e.g. Have found real cases where @ needs quoting. - # We list characters to allow, rather than characters not to allow. - NEW_PHRASE_UNSAFE=/[^A-Za-z0-9!#\$%&'*+\-\/=?^_`{|}~ ]/n - def quote_phrase( str ) - (NEW_PHRASE_UNSAFE === str) ? dquote(str) : str - end - end -end - -# Monkeypatch! TMail 1.2.7.1 will parse only one address out of a list of addresses with -# unquoted display parts https://github.com/mikel/tmail/issues#issue/9 - this monkeypatch -# fixes this issue. -module TMail - - class Parser < Racc::Parser - -module_eval <<'..end lib/tmail/parser.y modeval..id2dd1c7d21d', 'lib/tmail/parser.y', 340 - - def self.special_quote_address(str) #:nodoc: - # Takes a string which is an address and adds quotation marks to special - # edge case methods that the RACC parser can not handle. - # - # Right now just handles two edge cases: - # - # Full stop as the last character of the display name: - # Mikel L. - # Returns: - # "Mikel L." - # - # Unquoted @ symbol in the display name: - # mikel@me.com - # Returns: - # "mikel@me.com" - # - # Any other address not matching these patterns just gets returned as is. - case - # This handles the missing "" in an older version of Apple Mail.app - # around the display name when the display name contains a '@' - # like 'mikel@me.com ' - # Just quotes it to: '"mikel@me.com" ' - when str =~ /\A([^"][^<]+@[^>]+[^"])\s(<.*?>)\Z/ - return "\"#{$1}\" #{$2}" - # This handles cases where 'Mikel A. ' which is a trailing - # full stop before the address section. Just quotes it to - # '"Mikel A." ' - when str =~ /\A(.*?\.)\s(<.*?>)\s*\Z/ - return "\"#{$1}\" #{$2}" - else - str - end - end - -..end lib/tmail/parser.y modeval..id2dd1c7d21d - end # class Parser - -end # module TMail - - -- cgit v1.2.3 From a74855165779821ba531fd3f9c3767fc3d10ac60 Mon Sep 17 00:00:00 2001 From: Henare Degan Date: Sat, 2 Mar 2013 15:38:46 +1100 Subject: Backport newer Mail code to fix decoding problems. #850 --- lib/mail_handler/backends/mail_extensions.rb | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_extensions.rb b/lib/mail_handler/backends/mail_extensions.rb index 83dce5733..29ae32542 100644 --- a/lib/mail_handler/backends/mail_extensions.rb +++ b/lib/mail_handler/backends/mail_extensions.rb @@ -63,4 +63,42 @@ module Mail end.join(";\r\n\s") end end + + # HACK: Backport encoding fixes for Ruby 1.8 from Mail 2.5 + # Can be removed when we no longer support Ruby 1.8 + class Ruby18 + def Ruby18.b_value_decode(str) + match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m) + if match + encoding = match[1] + str = Ruby18.decode_base64(match[2]) + str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str) + end + str + end + + def Ruby18.q_value_decode(str) + match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m) + if match + encoding = match[1] + string = match[2].gsub(/_/, '=20') + # Remove trailing = if it exists in a Q encoding + string = string.sub(/\=$/, '') + str = Encodings::QuotedPrintable.decode(string) + str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str) + end + str + end + + private + + def Ruby18.fix_encoding(encoding) + case encoding.upcase + when 'UTF8' + 'UTF-8' + else + encoding + end + end + end end -- cgit v1.2.3 From 9564ca12234e27e2292b5382596664634d4994e0 Mon Sep 17 00:00:00 2001 From: Henare Degan Date: Sat, 2 Mar 2013 18:07:30 +1100 Subject: Backport Mail 2.5's to_yaml to prevent an exception with Ruby 1.8 --- lib/mail_handler/backends/mail_extensions.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_extensions.rb b/lib/mail_handler/backends/mail_extensions.rb index 29ae32542..c9f16e361 100644 --- a/lib/mail_handler/backends/mail_extensions.rb +++ b/lib/mail_handler/backends/mail_extensions.rb @@ -46,6 +46,28 @@ module Mail self end + + # HACK: Backported from Mail 2.5 for Ruby 1.8 support + # Can be removed when we no longer support Ruby 1.8 + def to_yaml(opts = {}) + hash = {} + hash['headers'] = {} + header.fields.each do |field| + hash['headers'][field.name] = field.value + end + hash['delivery_handler'] = delivery_handler.to_s if delivery_handler + hash['transport_encoding'] = transport_encoding.to_s + special_variables = [:@header, :@delivery_handler, :@transport_encoding] + if multipart? + hash['multipart_body'] = [] + body.parts.map { |part| hash['multipart_body'] << part.to_yaml } + special_variables.push(:@body, :@text_part, :@html_part) + end + (instance_variables.map(&:to_sym) - special_variables).each do |var| + hash[var.to_s] = instance_variable_get(var) + end + hash.to_yaml(opts) + end end # A patched version of the parameter hash that handles nil values without throwing -- cgit v1.2.3 From 930619d4cf3a779abaf28e4cfa9d287e552bdc05 Mon Sep 17 00:00:00 2001 From: Henare Degan Date: Tue, 26 Feb 2013 16:07:46 +1100 Subject: Revert "Update this to match the older version of the mail gem we're using" This reverts commit b9a2d996408c1ef703aa78311f8c2a0f6e0d7afc. --- lib/mail_handler/backends/mail_extensions.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_extensions.rb b/lib/mail_handler/backends/mail_extensions.rb index c9f16e361..34ced7319 100644 --- a/lib/mail_handler/backends/mail_extensions.rb +++ b/lib/mail_handler/backends/mail_extensions.rb @@ -72,7 +72,8 @@ module Mail # A patched version of the parameter hash that handles nil values without throwing # an error. - class ParameterHash < HashWithIndifferentAccess + class ParameterHash < IndifferentHash + def encoded map.sort { |a,b| a.first.to_s <=> b.first.to_s }.map do |key_name, value| # The replacement of this commented out line is the change -- cgit v1.2.3 From 2f6af09899e2822121d060db55fdd78b15f099db Mon Sep 17 00:00:00 2001 From: Henare Degan Date: Thu, 7 Mar 2013 18:16:16 +1100 Subject: Backport Mail's encoding code from 2.5 for Ruby 1.9 Decoding messages in Ruby 1.9 was screwing up but not dying like 1.8. Backporting this fixes the problem. --- lib/mail_handler/backends/mail_extensions.rb | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_extensions.rb b/lib/mail_handler/backends/mail_extensions.rb index 34ced7319..611b44c4c 100644 --- a/lib/mail_handler/backends/mail_extensions.rb +++ b/lib/mail_handler/backends/mail_extensions.rb @@ -124,4 +124,49 @@ module Mail end end end + + # HACK: Backport encoding fixes for Ruby 1.9 from Mail 2.5 + # Can be removed when Rails relies on Mail > 2.5 + class Ruby19 + def Ruby19.b_value_decode(str) + match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m) + if match + encoding = match[1] + str = Ruby19.decode_base64(match[2]) + str.force_encoding(fix_encoding(encoding)) + end + decoded = str.encode("utf-8", :invalid => :replace, :replace => "") + decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8") + end + + def Ruby19.q_value_decode(str) + match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m) + if match + encoding = match[1] + str = Encodings::QuotedPrintable.decode(match[2]) + str.force_encoding(fix_encoding(encoding)) + end + decoded = str.encode("utf-8", :invalid => :replace, :replace => "") + decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8") + end + + # mails somtimes includes invalid encodings like iso885915 or utf8 so we transform them to iso885915 or utf8 + # TODO: add this as a test somewhere + # Encoding.list.map{|e| [e.to_s.upcase==fix_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b} + # Encoding.list.map{|e| [e.to_s==fix_encoding(e.to_s), e.to_s] }.select {|a,b| !b} + def Ruby19.fix_encoding(encoding) + case encoding + # ISO-8859-15, ISO-2022-JP and alike + when /iso-?(\d{4})-?(\w{1,2})/i then return "ISO-#{$1}-#{$2}" + # "ISO-2022-JP-KDDI" and alike + when /iso-?(\d{4})-?(\w{1,2})-?(\w*)/i then return "ISO-#{$1}-#{$2}-#{$3}" + # UTF-8, UTF-32BE and alike + when /utf-?(\d{1,2})?(\w{1,2})/i then return "UTF-#{$1}#{$2}" + # Windows-1252 and alike + when /Windows-?(.*)/i then return "Windows-#{$1}" + #more aliases to be added if needed + else return encoding + end + end + end end -- cgit v1.2.3 From 5738367d0f38627ba0227758be0433af23faefbc Mon Sep 17 00:00:00 2001 From: Matthew Landauer Date: Tue, 19 Mar 2013 11:41:19 +1100 Subject: Rails security update The rails upgrade also forces a mail gem upgrade. To make things work again we need to remove part of the monkeypatched backporting of encoding fixes. --- lib/mail_handler/backends/mail_extensions.rb | 67 ---------------------------- 1 file changed, 67 deletions(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_extensions.rb b/lib/mail_handler/backends/mail_extensions.rb index 611b44c4c..d25012e39 100644 --- a/lib/mail_handler/backends/mail_extensions.rb +++ b/lib/mail_handler/backends/mail_extensions.rb @@ -46,28 +46,6 @@ module Mail self end - - # HACK: Backported from Mail 2.5 for Ruby 1.8 support - # Can be removed when we no longer support Ruby 1.8 - def to_yaml(opts = {}) - hash = {} - hash['headers'] = {} - header.fields.each do |field| - hash['headers'][field.name] = field.value - end - hash['delivery_handler'] = delivery_handler.to_s if delivery_handler - hash['transport_encoding'] = transport_encoding.to_s - special_variables = [:@header, :@delivery_handler, :@transport_encoding] - if multipart? - hash['multipart_body'] = [] - body.parts.map { |part| hash['multipart_body'] << part.to_yaml } - special_variables.push(:@body, :@text_part, :@html_part) - end - (instance_variables.map(&:to_sym) - special_variables).each do |var| - hash[var.to_s] = instance_variable_get(var) - end - hash.to_yaml(opts) - end end # A patched version of the parameter hash that handles nil values without throwing @@ -124,49 +102,4 @@ module Mail end end end - - # HACK: Backport encoding fixes for Ruby 1.9 from Mail 2.5 - # Can be removed when Rails relies on Mail > 2.5 - class Ruby19 - def Ruby19.b_value_decode(str) - match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m) - if match - encoding = match[1] - str = Ruby19.decode_base64(match[2]) - str.force_encoding(fix_encoding(encoding)) - end - decoded = str.encode("utf-8", :invalid => :replace, :replace => "") - decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8") - end - - def Ruby19.q_value_decode(str) - match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m) - if match - encoding = match[1] - str = Encodings::QuotedPrintable.decode(match[2]) - str.force_encoding(fix_encoding(encoding)) - end - decoded = str.encode("utf-8", :invalid => :replace, :replace => "") - decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8") - end - - # mails somtimes includes invalid encodings like iso885915 or utf8 so we transform them to iso885915 or utf8 - # TODO: add this as a test somewhere - # Encoding.list.map{|e| [e.to_s.upcase==fix_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b} - # Encoding.list.map{|e| [e.to_s==fix_encoding(e.to_s), e.to_s] }.select {|a,b| !b} - def Ruby19.fix_encoding(encoding) - case encoding - # ISO-8859-15, ISO-2022-JP and alike - when /iso-?(\d{4})-?(\w{1,2})/i then return "ISO-#{$1}-#{$2}" - # "ISO-2022-JP-KDDI" and alike - when /iso-?(\d{4})-?(\w{1,2})-?(\w*)/i then return "ISO-#{$1}-#{$2}-#{$3}" - # UTF-8, UTF-32BE and alike - when /utf-?(\d{1,2})?(\w{1,2})/i then return "UTF-#{$1}#{$2}" - # Windows-1252 and alike - when /Windows-?(.*)/i then return "Windows-#{$1}" - #more aliases to be added if needed - else return encoding - end - end - end end -- cgit v1.2.3 From 2805cc72a21089ea9725352b1d4d39d7d929a7a0 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Thu, 2 May 2013 15:34:08 +0100 Subject: Ignore common TNEF attachment parsing errors This also introduces a custom error class so that we don't accidentally catch other problems. Fixes #920 --- lib/mail_handler/backends/mail_backend.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb index f7893a60d..bd3a91569 100644 --- a/lib/mail_handler/backends/mail_backend.rb +++ b/lib/mail_handler/backends/mail_backend.rb @@ -141,9 +141,14 @@ module MailHandler end elsif get_content_type(part) == 'application/ms-tnef' # A set of attachments in a TNEF file - part.rfc822_attachment = mail_from_tnef(part.body.decoded) - if part.rfc822_attachment.nil? - # Attached mail didn't parse, so treat as binary + begin + part.rfc822_attachment = mail_from_tnef(part.body.decoded) + if part.rfc822_attachment.nil? + # Attached mail didn't parse, so treat as binary + part.content_type = 'application/octet-stream' + end + rescue TNEFParsingError + part.rfc822_attachment = nil part.content_type = 'application/octet-stream' end end -- cgit v1.2.3 From 2d5ddc2d37837cb9ee80931b09dff04cc1120bda Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Wed, 15 May 2013 14:52:24 +0100 Subject: Make efforts to ensure that we're usually dealing with UTF-8 strings One of these changes is to make sure that the Mail backend, like the TMail backend it replaces, will return text parts encoded in UTF-8 if possible. The other change is to ensure that when text attachments are reloaded from disk, we attempt to convert them to UTF-8. --- lib/mail_handler/backends/mail_backend.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb index bd3a91569..f0dfc843c 100644 --- a/lib/mail_handler/backends/mail_backend.rb +++ b/lib/mail_handler/backends/mail_backend.rb @@ -38,7 +38,11 @@ module MailHandler # Get the body of a mail part def get_part_body(part) - part.body.decoded + decoded = part.body.decoded + if part.content_type =~ /^text\// + decoded = convert_string_to_utf8_or_binary decoded, part.charset + end + decoded end # Return the first from field if any -- cgit v1.2.3 From e01774371ae7b4a5f9e3ef8ee0280453736bd4f2 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Thu, 16 May 2013 16:37:46 +0100 Subject: Only set original_charset when a charset has been defined for the mail part. Fixes #942. --- lib/mail_handler/backends/mail_backend.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb index f0dfc843c..6c213d370 100644 --- a/lib/mail_handler/backends/mail_backend.rb +++ b/lib/mail_handler/backends/mail_backend.rb @@ -169,8 +169,11 @@ module MailHandler part.parts.each{ |sub_part| expand_and_normalize_parts(sub_part, parent_mail) } else part_filename = get_part_file_name(part) - charset = part.charset # save this, because overwriting content_type also resets charset - + if part.has_charset? + original_charset = part.charset # save this, because overwriting content_type also resets charset + else + original_charset = nil + end # Don't allow nil content_types if get_content_type(part).nil? part.content_type = 'application/octet-stream' @@ -189,7 +192,9 @@ module MailHandler # Use standard content types for Word documents etc. part.content_type = normalise_content_type(get_content_type(part)) decode_attached_part(part, parent_mail) - part.charset = charset + if original_charset + part.charset = original_charset + end end end -- cgit v1.2.3 From 46e7df935929793fafb6069fbd272f5a35752e89 Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Fri, 17 May 2013 11:48:14 +0100 Subject: Cope with emails with a missing final MIME boundary The Mail gem deals with multipart messages that look as if they should have 1 part but are missing the final MIME boundary, by make the parts list empty and setting part.body to the text of the email. Rather than throwing an exception in this case, we just pretend that part is text/plain and return it, so that the page doesn't error and we still have a chance of some useful text being displayed. Note that we haven't investigated yet the case of emails that have more than one start boundary, but no final boundary. Fixes #921 --- lib/mail_handler/backends/mail_backend.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb index 6c213d370..a97e68138 100644 --- a/lib/mail_handler/backends/mail_backend.rb +++ b/lib/mail_handler/backends/mail_backend.rb @@ -242,8 +242,15 @@ module MailHandler def _get_attachment_leaves_recursive(part, within_rfc822_attachment, parent_mail) leaves_found = [] if part.multipart? - raise "no parts on multipart mail" if part.parts.size == 0 - if part.sub_type == 'alternative' + if part.parts.size == 0 + # This is typically caused by a missing final + # MIME boundary, in which case the text of the + # message (including the opening MIME + # boundary) is in part.body, so just add this + # part as a leaf and treat it as text/plain: + part.content_type = "text/plain" + leaves_found += [part] + elsif part.sub_type == 'alternative' best_part = choose_best_alternative(part) leaves_found += _get_attachment_leaves_recursive(best_part, within_rfc822_attachment, -- cgit v1.2.3 From 6e64eb8fd3a346c24990553f294fb9d1f0ae6bbc Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Tue, 21 May 2013 17:03:08 +0100 Subject: Retain old handling of malformed addresses in To and Cc lines The behaviour of the TMail backend's 'to' and 'cc' methods where there was a malformed To: or Cc: line was to return nil, whereas Mail returns a version of the string anyway. We'd have to change quite a lot of code to deal with an extra possible class of returned objects, so it's simplest for the moment to monkey-patch Mail::Message's 'to' and 'cc' methods to restore the old behaviour. --- lib/mail_handler/backends/mail_backend.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb index a97e68138..42180dd6f 100644 --- a/lib/mail_handler/backends/mail_backend.rb +++ b/lib/mail_handler/backends/mail_backend.rb @@ -1,5 +1,34 @@ require 'mail' +module Mail + class Message + + # The behaviour of the 'to' and 'cc' methods have changed + # between TMail and Mail; this monkey-patching restores the + # TMail behaviour. The key difference is that when there's an + # invalid address, e.g. ' Date: Wed, 22 May 2013 13:47:01 +0100 Subject: Move the mapi requires to where they're really needed Handling of outlook-packed attachments would fail from rake tasks or in the console without requiring 'mapi/msg' and 'mapi/convert' beforehand. Instead, require them in the source file where they're actually used. --- lib/mail_handler/backends/mail_backend.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb index 42180dd6f..03d78e0a3 100644 --- a/lib/mail_handler/backends/mail_backend.rb +++ b/lib/mail_handler/backends/mail_backend.rb @@ -1,4 +1,6 @@ require 'mail' +require 'mapi/msg' +require 'mapi/convert' module Mail class Message -- cgit v1.2.3 From d5725cac044cc46245edc209e7c61c717e0d23db Mon Sep 17 00:00:00 2001 From: Mark Longair Date: Mon, 3 Jun 2013 15:11:05 +0100 Subject: Fix for subject lines with invalid UTF-8 as the last character This seems to be the bug mentioned here: http://po-ru.com/diary/fixing-invalid-utf-8-in-ruby-revisited/ That explains that some versions of Iconv don't ignore invalid characters when converting to UTF-8 even with //IGNORE if that invalid character happens to be at the end of the string. In fact, as Matthew Somerville pointed out, with some versions of iconv (e.g. 1.14 on Mac OS, apparently) it's necessary to add and remove more than one space at the end, in case the first character of the byte sequence indicates a long sequence. We add and remove 4 to be on the safe side. --- lib/mail_handler/backends/mail_extensions.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_extensions.rb b/lib/mail_handler/backends/mail_extensions.rb index d25012e39..54599639b 100644 --- a/lib/mail_handler/backends/mail_extensions.rb +++ b/lib/mail_handler/backends/mail_extensions.rb @@ -73,7 +73,12 @@ module Mail if match encoding = match[1] str = Ruby18.decode_base64(match[2]) - str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str) + # Adding and removing trailing spaces is a workaround + # for Iconv.conv throwing an exception if it finds an + # invalid character at the end of the string, even + # with UTF-8//IGNORE: + # http://po-ru.com/diary/fixing-invalid-utf-8-in-ruby-revisited/ + str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str + " ")[0...-4] end str end @@ -86,7 +91,12 @@ module Mail # Remove trailing = if it exists in a Q encoding string = string.sub(/\=$/, '') str = Encodings::QuotedPrintable.decode(string) - str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str) + # Adding and removing trailing spaces is a workaround + # for Iconv.conv throwing an exception if it finds an + # invalid character at the end of the string, even + # with UTF-8//IGNORE: + # http://po-ru.com/diary/fixing-invalid-utf-8-in-ruby-revisited/ + str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str + " ")[0...-4] end str end -- cgit v1.2.3 From e503bf89c973dad5bdbffb3e2ec4d15cf063bf91 Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Mon, 3 Jun 2013 13:10:46 +0100 Subject: Parse the 'to' address as if on a real mail to trigger quoted string encoding. --- lib/mail_handler/backends/mail_backend.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb index 03d78e0a3..561946980 100644 --- a/lib/mail_handler/backends/mail_backend.rb +++ b/lib/mail_handler/backends/mail_backend.rb @@ -367,7 +367,9 @@ module MailHandler end def address_from_string(string) - Mail::Address.new(string).address + mail = Mail.new + mail.from = string + mail.from[0] end end end -- cgit v1.2.3 From a919141992a40599f99b32bd4a8312a0009f3f7a Mon Sep 17 00:00:00 2001 From: Louise Crow Date: Tue, 4 Jun 2013 10:29:56 +0100 Subject: Backport ruby 1.9 fix for trailing = sign in message headers from mail 2.5 --- lib/mail_handler/backends/mail_extensions.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'lib/mail_handler/backends') diff --git a/lib/mail_handler/backends/mail_extensions.rb b/lib/mail_handler/backends/mail_extensions.rb index 54599639b..322c49bb5 100644 --- a/lib/mail_handler/backends/mail_extensions.rb +++ b/lib/mail_handler/backends/mail_extensions.rb @@ -112,4 +112,20 @@ module Mail end end end + class Ruby19 + + def Ruby19.q_value_decode(str) + match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m) + if match + encoding = match[1] + str = Encodings::QuotedPrintable.decode(match[2].gsub(/_/, '=20')) + # Backport line from mail 2.5 to strip a trailing = character + # Remove trailing = if it exists in a Q encoding + str = str.sub(/\=$/, '') + str.force_encoding(fix_encoding(encoding)) + end + decoded = str.encode("utf-8", :invalid => :replace, :replace => "") + decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8") + end + end end -- cgit v1.2.3