aboutsummaryrefslogtreecommitdiffstats
path: root/lib/mail_handler
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mail_handler')
-rw-r--r--lib/mail_handler/backends/mail_backend.rb76
-rw-r--r--lib/mail_handler/backends/mail_extensions.rb48
-rw-r--r--lib/mail_handler/mail_handler.rb12
3 files changed, 119 insertions, 17 deletions
diff --git a/lib/mail_handler/backends/mail_backend.rb b/lib/mail_handler/backends/mail_backend.rb
index f7893a60d..28c486e1b 100644
--- a/lib/mail_handler/backends/mail_backend.rb
+++ b/lib/mail_handler/backends/mail_backend.rb
@@ -1,4 +1,35 @@
require 'mail'
+require 'mapi/msg'
+require 'mapi/convert'
+
+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. '<foo@example.org', Mail returns the
+ # string as an ActiveSupport::Multibyte::Chars, whereas
+ # previously TMail would return nil.
+
+ alias_method :old_to, :to
+ alias_method :old_cc, :cc
+
+ def clean_addresses(old_method, val)
+ old_result = self.send(old_method, val)
+ old_result.class == Mail::AddressContainer ? old_result : nil
+ end
+
+ def to(val = nil)
+ self.clean_addresses :old_to, val
+ end
+
+ def cc(val = nil)
+ self.clean_addresses :old_cc, val
+ end
+
+ end
+end
module MailHandler
module Backends
@@ -38,7 +69,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
@@ -77,7 +112,7 @@ module MailHandler
if first_from.is_a?(ActiveSupport::Multibyte::Chars)
return nil
else
- return first_from.display_name ? eval(%Q{"#{first_from.display_name}"}) : nil
+ return (first_from.display_name || nil)
end
else
return nil
@@ -141,9 +176,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
@@ -160,8 +200,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'
@@ -180,7 +223,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
@@ -228,8 +273,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,
@@ -315,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
diff --git a/lib/mail_handler/backends/mail_extensions.rb b/lib/mail_handler/backends/mail_extensions.rb
index d25012e39..a5b6bc97e 100644
--- a/lib/mail_handler/backends/mail_extensions.rb
+++ b/lib/mail_handler/backends/mail_extensions.rb
@@ -73,7 +73,15 @@ 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/
+ begin
+ str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str + " ")[0...-4]
+ rescue Iconv::InvalidEncoding
+ end
end
str
end
@@ -86,7 +94,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
@@ -102,4 +115,35 @@ module Mail
end
end
end
+ class Ruby19
+
+ def Ruby19.b_value_decode(str)
+ match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m)
+ if match
+ encoding = match[1]
+ str = Ruby19.decode_base64(match[2])
+ # Rescue an ArgumentError arising from an unknown encoding.
+ begin
+ str.force_encoding(fix_encoding(encoding))
+ rescue ArgumentError
+ end
+ 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].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
diff --git a/lib/mail_handler/mail_handler.rb b/lib/mail_handler/mail_handler.rb
index d9ebee854..9c955cccd 100644
--- a/lib/mail_handler/mail_handler.rb
+++ b/lib/mail_handler/mail_handler.rb
@@ -8,20 +8,23 @@ module MailHandler
require 'backends/mail_backend'
include Backends::MailBackend
+ class TNEFParsingError < StandardError
+ end
+
# Returns a set of attachments from the given TNEF contents
# The TNEF contents also contains the message body, but in general this is the
# same as the message body in the message proper.
def tnef_attachments(content)
attachments = []
Dir.mktmpdir do |dir|
- IO.popen("#{`which tnef`.chomp} -K -C #{dir}", "wb") do |f|
+ IO.popen("tnef -K -C #{dir} 2> /dev/null", "wb") do |f|
f.write(content)
f.close
if $?.signaled?
raise IOError, "tnef exited with signal #{$?.termsig}"
end
if $?.exited? && $?.exitstatus != 0
- raise IOError, "tnef exited with status #{$?.exitstatus}"
+ raise TNEFParsingError, "tnef exited with status #{$?.exitstatus}"
end
end
found = 0
@@ -34,7 +37,7 @@ module MailHandler
end
end
if found == 0
- raise IOError, "tnef produced no attachments"
+ raise TNEFParsingError, "tnef produced no attachments"
end
end
attachments
@@ -77,7 +80,8 @@ module MailHandler
tempfile.flush
default_params = { :append_to => text, :binary_output => false }
if content_type == 'application/vnd.ms-word'
- AlaveteliExternalCommand.run("wvText", tempfile.path, tempfile.path + ".txt")
+ AlaveteliExternalCommand.run("wvText", tempfile.path, tempfile.path + ".txt",
+ { :memory_limit => 536870912 } )
# Try catdoc if we get into trouble (e.g. for InfoRequestEvent 2701)
if not File.exists?(tempfile.path + ".txt")
AlaveteliExternalCommand.run("catdoc", tempfile.path, default_params)