mail 2.3.3 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of mail might be problematic. Click here for more details.
- data/CHANGELOG.rdoc +26 -0
- data/CONTRIBUTING.md +45 -0
- data/Gemfile +2 -8
- data/Gemfile.lock +31 -0
- data/README.md +649 -0
- data/Rakefile +4 -30
- data/lib/VERSION +2 -2
- data/lib/mail.rb +3 -1
- data/lib/mail/attachments_list.rb +2 -3
- data/lib/mail/body.rb +1 -2
- data/lib/mail/configuration.rb +3 -3
- data/lib/mail/core_extensions/nil.rb +4 -0
- data/lib/mail/core_extensions/shellwords.rb +57 -0
- data/lib/mail/core_extensions/string.rb +6 -4
- data/lib/mail/core_extensions/string/access.rb +41 -0
- data/lib/mail/encodings.rb +8 -8
- data/lib/mail/field.rb +2 -0
- data/lib/mail/fields/common/parameter_hash.rb +1 -1
- data/lib/mail/fields/unstructured_field.rb +1 -1
- data/lib/mail/matchers/has_sent_mail.rb +124 -0
- data/lib/mail/message.rb +72 -19
- data/lib/mail/multibyte/chars.rb +2 -2
- data/lib/mail/multibyte/utils.rb +1 -1
- data/lib/mail/network/delivery_methods/exim.rb +5 -38
- data/lib/mail/network/delivery_methods/file_delivery.rb +2 -2
- data/lib/mail/network/delivery_methods/sendmail.rb +3 -3
- data/lib/mail/network/delivery_methods/smtp.rb +20 -4
- data/lib/mail/network/retriever_methods/imap.rb +4 -2
- data/lib/mail/parsers/rfc2822.treetop +1 -1
- data/lib/mail/parsers/rfc2822_obsolete.rb +14 -3
- data/lib/mail/parsers/rfc2822_obsolete.treetop +2 -2
- data/lib/mail/parts_list.rb +4 -0
- data/lib/mail/utilities.rb +6 -2
- data/lib/mail/version_specific/ruby_1_8.rb +1 -1
- data/lib/mail/version_specific/ruby_1_9.rb +8 -4
- metadata +18 -12
- data/README.rdoc +0 -563
- data/lib/mail/core_extensions/shell_escape.rb +0 -56
data/lib/mail/message.rb
CHANGED
@@ -10,7 +10,7 @@ module Mail
|
|
10
10
|
#
|
11
11
|
# A Message object by default has the following objects inside it:
|
12
12
|
#
|
13
|
-
# * A Header object which
|
13
|
+
# * A Header object which contains all information and settings of the header of the email
|
14
14
|
# * Body object which contains all parts of the email that are not part of the header, this
|
15
15
|
# includes any attachments, body text, MIME parts etc.
|
16
16
|
#
|
@@ -100,7 +100,6 @@ module Mail
|
|
100
100
|
def initialize(*args, &block)
|
101
101
|
@body = nil
|
102
102
|
@body_raw = nil
|
103
|
-
@body_raw_index = nil
|
104
103
|
@separate_parts = false
|
105
104
|
@text_part = nil
|
106
105
|
@html_part = nil
|
@@ -1134,7 +1133,7 @@ module Mail
|
|
1134
1133
|
# mail.parts.length #=> 2
|
1135
1134
|
# mail.parts.last.content_type.content_type #=> 'This is a body'
|
1136
1135
|
def body=(value)
|
1137
|
-
body_lazy(value
|
1136
|
+
body_lazy(value)
|
1138
1137
|
end
|
1139
1138
|
|
1140
1139
|
# Returns the body of the message object. Or, if passed
|
@@ -1711,19 +1710,54 @@ module Mail
|
|
1711
1710
|
buffer
|
1712
1711
|
end
|
1713
1712
|
|
1714
|
-
def
|
1715
|
-
|
1713
|
+
def without_attachments!
|
1714
|
+
return self unless has_attachments?
|
1715
|
+
|
1716
|
+
parts.delete_if { |p| p.attachment? }
|
1717
|
+
body_raw = if parts.empty?
|
1718
|
+
''
|
1719
|
+
else
|
1720
|
+
body.encoded
|
1721
|
+
end
|
1722
|
+
|
1723
|
+
@body = Mail::Body.new(body_raw)
|
1724
|
+
|
1725
|
+
self
|
1726
|
+
end
|
1727
|
+
|
1728
|
+
def to_yaml(opts = {})
|
1716
1729
|
hash = {}
|
1730
|
+
hash['headers'] = {}
|
1717
1731
|
header.fields.each do |field|
|
1718
|
-
hash[field.name] = field.value
|
1732
|
+
hash['headers'][field.name] = field.value
|
1733
|
+
end
|
1734
|
+
hash['delivery_handler'] = delivery_handler.to_s if delivery_handler
|
1735
|
+
hash['transport_encoding'] = transport_encoding.to_s
|
1736
|
+
special_variables = [:@header, :@delivery_handler, :@transport_encoding]
|
1737
|
+
(instance_variables.map(&:to_sym) - special_variables).each do |var|
|
1738
|
+
hash[var.to_s] = instance_variable_get(var)
|
1719
1739
|
end
|
1720
|
-
hash
|
1721
|
-
hash['body'] = body.encoded(content_transfer_encoding)
|
1722
|
-
hash.to_yaml
|
1740
|
+
hash.to_yaml(opts)
|
1723
1741
|
end
|
1724
1742
|
|
1725
1743
|
def self.from_yaml(str)
|
1726
|
-
|
1744
|
+
hash = YAML.load(str)
|
1745
|
+
m = Mail::Message.new(:headers => hash['headers'])
|
1746
|
+
hash.delete('headers')
|
1747
|
+
hash.each do |k,v|
|
1748
|
+
case
|
1749
|
+
when k == 'delivery_handler'
|
1750
|
+
begin
|
1751
|
+
m.delivery_handler = Object.const_get(v) unless v.blank?
|
1752
|
+
rescue NameError
|
1753
|
+
end
|
1754
|
+
when k == 'transport_encoding'
|
1755
|
+
m.transport_encoding(v)
|
1756
|
+
when k =~ /^@/
|
1757
|
+
m.instance_variable_set(k.to_sym, v)
|
1758
|
+
end
|
1759
|
+
end
|
1760
|
+
m
|
1727
1761
|
end
|
1728
1762
|
|
1729
1763
|
def self.from_hash(hash)
|
@@ -1740,6 +1774,8 @@ module Mail
|
|
1740
1774
|
|
1741
1775
|
def decoded
|
1742
1776
|
case
|
1777
|
+
when self.text?
|
1778
|
+
decode_body_as_text
|
1743
1779
|
when self.attachment?
|
1744
1780
|
decode_body
|
1745
1781
|
when !self.multipart?
|
@@ -1814,6 +1850,10 @@ module Mail
|
|
1814
1850
|
return @mark_for_delete
|
1815
1851
|
end
|
1816
1852
|
|
1853
|
+
def text?
|
1854
|
+
has_content_type? ? !!(main_type =~ /^text$/i) : false
|
1855
|
+
end
|
1856
|
+
|
1817
1857
|
private
|
1818
1858
|
|
1819
1859
|
# 2.1. General Description
|
@@ -1839,31 +1879,28 @@ module Mail
|
|
1839
1879
|
@raw_source = value.to_crlf
|
1840
1880
|
end
|
1841
1881
|
|
1842
|
-
# see comments to body=. We take data
|
1843
|
-
def body_lazy(value
|
1882
|
+
# see comments to body=. We take data and process it lazily
|
1883
|
+
def body_lazy(value)
|
1844
1884
|
process_body_raw if @body_raw && value
|
1845
1885
|
case
|
1846
|
-
when value == nil || value.length<=
|
1886
|
+
when value == nil || value.length<=0
|
1847
1887
|
@body = Mail::Body.new('')
|
1848
1888
|
@body_raw = nil
|
1849
|
-
@body_raw_index = nil
|
1850
1889
|
add_encoding_to_body
|
1851
1890
|
when @body && @body.multipart?
|
1852
|
-
@body << Mail::Part.new(value
|
1891
|
+
@body << Mail::Part.new(value)
|
1853
1892
|
add_encoding_to_body
|
1854
1893
|
else
|
1855
1894
|
@body_raw = value
|
1856
|
-
@body_raw_index = index
|
1857
1895
|
# process_body_raw
|
1858
1896
|
end
|
1859
1897
|
end
|
1860
1898
|
|
1861
1899
|
|
1862
1900
|
def process_body_raw
|
1863
|
-
@body = Mail::Body.new(@body_raw
|
1901
|
+
@body = Mail::Body.new(@body_raw)
|
1864
1902
|
@body_raw = nil
|
1865
|
-
|
1866
|
-
separate_parts if @separate_parts
|
1903
|
+
separate_parts if @separate_parts
|
1867
1904
|
|
1868
1905
|
add_encoding_to_body
|
1869
1906
|
end
|
@@ -1993,5 +2030,21 @@ module Mail
|
|
1993
2030
|
end
|
1994
2031
|
end
|
1995
2032
|
|
2033
|
+
def decode_body_as_text
|
2034
|
+
body_text = decode_body
|
2035
|
+
if charset
|
2036
|
+
if RUBY_VERSION < '1.9'
|
2037
|
+
require 'iconv'
|
2038
|
+
return Iconv.conv("UTF-8//TRANSLIT//IGNORE", charset, body_text)
|
2039
|
+
else
|
2040
|
+
if encoding = Encoding.find(charset) rescue nil
|
2041
|
+
body_text.force_encoding(encoding)
|
2042
|
+
return body_text.encode(Encoding::UTF_8)
|
2043
|
+
end
|
2044
|
+
end
|
2045
|
+
end
|
2046
|
+
body_text
|
2047
|
+
end
|
2048
|
+
|
1996
2049
|
end
|
1997
2050
|
end
|
data/lib/mail/multibyte/chars.rb
CHANGED
@@ -339,7 +339,7 @@ module Mail #:nodoc:
|
|
339
339
|
# Example:
|
340
340
|
# 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
|
341
341
|
def upcase
|
342
|
-
chars(Unicode.apply_mapping
|
342
|
+
chars(Unicode.apply_mapping(@wrapped_string), :uppercase_mapping)
|
343
343
|
end
|
344
344
|
|
345
345
|
# Convert characters in the string to lowercase.
|
@@ -347,7 +347,7 @@ module Mail #:nodoc:
|
|
347
347
|
# Example:
|
348
348
|
# 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
|
349
349
|
def downcase
|
350
|
-
chars(Unicode.apply_mapping
|
350
|
+
chars(Unicode.apply_mapping(@wrapped_string), :lowercase_mapping)
|
351
351
|
end
|
352
352
|
|
353
353
|
# Converts the first character to uppercase and the remainder to lowercase.
|
data/lib/mail/multibyte/utils.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Mail #:nodoc:
|
4
4
|
module Multibyte #:nodoc:
|
5
|
-
if
|
5
|
+
if RUBY_VERSION >= "1.9"
|
6
6
|
# Returns a regular expression that matches valid characters in the current encoding
|
7
7
|
def self.valid_character
|
8
8
|
VALID_CHARACTER[Encoding.default_external.to_s]
|
@@ -1,45 +1,12 @@
|
|
1
1
|
module Mail
|
2
2
|
|
3
|
-
# A delivery method implementation which sends via exim.
|
4
|
-
#
|
5
|
-
# To use this, first find out where the exim binary is on your computer,
|
6
|
-
# if you are on a mac or unix box, it is usually in /usr/sbin/exim, this will
|
7
|
-
# be your exim location.
|
8
|
-
#
|
9
|
-
# Mail.defaults do
|
10
|
-
# delivery_method :exim
|
11
|
-
# end
|
12
|
-
#
|
13
|
-
# Or if your exim binary is not at '/usr/sbin/exim'
|
14
|
-
#
|
15
|
-
# Mail.defaults do
|
16
|
-
# delivery_method :exim, :location => '/absolute/path/to/your/exim'
|
17
|
-
# end
|
18
|
-
#
|
19
|
-
# Then just deliver the email as normal:
|
20
|
-
#
|
21
|
-
# Mail.deliver do
|
22
|
-
# to 'mikel@test.lindsaar.net'
|
23
|
-
# from 'ada@test.lindsaar.net'
|
24
|
-
# subject 'testing exim'
|
25
|
-
# body 'testing exim'
|
26
|
-
# end
|
27
|
-
#
|
28
|
-
# Or by calling deliver on a Mail message
|
29
|
-
#
|
30
|
-
# mail = Mail.new do
|
31
|
-
# to 'mikel@test.lindsaar.net'
|
32
|
-
# from 'ada@test.lindsaar.net'
|
33
|
-
# subject 'testing exim'
|
34
|
-
# body 'testing exim'
|
35
|
-
# end
|
36
|
-
#
|
37
|
-
# mail.deliver!
|
38
3
|
class Exim < Sendmail
|
39
4
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
5
|
+
def deliver!(mail)
|
6
|
+
envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
|
7
|
+
return_path = "-f \"#{envelope_from.to_s.shellescape}\"" if envelope_from
|
8
|
+
arguments = [settings[:arguments], return_path].compact.join(" ")
|
9
|
+
self.class.call(settings[:location], arguments, mail)
|
43
10
|
end
|
44
11
|
|
45
12
|
def self.call(path, arguments, mail)
|
@@ -6,7 +6,7 @@ module Mail
|
|
6
6
|
# So if you have an email going to fred@test, bob@test, joe@anothertest, and you
|
7
7
|
# set your location path to /path/to/mails then FileDelivery will create the directory
|
8
8
|
# if it does not exist, and put one copy of the email in three files, called
|
9
|
-
#
|
9
|
+
# "fred@test", "bob@test" and "joe@anothertest"
|
10
10
|
#
|
11
11
|
# Make sure the path you specify with :location is writable by the Ruby process
|
12
12
|
# running Mail.
|
@@ -32,7 +32,7 @@ module Mail
|
|
32
32
|
end
|
33
33
|
|
34
34
|
mail.destinations.uniq.each do |to|
|
35
|
-
::File.open(::File.join(settings[:location],
|
35
|
+
::File.open(::File.join(settings[:location], to), 'a') { |f| "#{f.write(mail.encoded)}\r\n\r\n" }
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
@@ -45,14 +45,14 @@ module Mail
|
|
45
45
|
|
46
46
|
def deliver!(mail)
|
47
47
|
envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
|
48
|
-
return_path = "-f "
|
48
|
+
return_path = "-f \"#{envelope_from.to_s.gsub('"', '\"')}\"" if envelope_from
|
49
49
|
|
50
50
|
arguments = [settings[:arguments], return_path].compact.join(" ")
|
51
51
|
|
52
|
-
|
52
|
+
Sendmail.call(settings[:location], arguments, mail.destinations.collect(&:shellescape).join(" "), mail)
|
53
53
|
end
|
54
54
|
|
55
|
-
def
|
55
|
+
def Sendmail.call(path, arguments, destinations, mail)
|
56
56
|
IO.popen("#{path} #{arguments} #{destinations}", "w+") do |io|
|
57
57
|
io.puts mail.encoded.to_lf
|
58
58
|
io.flush
|
@@ -81,7 +81,9 @@ module Mail
|
|
81
81
|
:password => nil,
|
82
82
|
:authentication => nil,
|
83
83
|
:enable_starttls_auto => true,
|
84
|
-
:openssl_verify_mode => nil
|
84
|
+
:openssl_verify_mode => nil,
|
85
|
+
:ssl => nil,
|
86
|
+
:tls => nil
|
85
87
|
}.merge!(values)
|
86
88
|
end
|
87
89
|
|
@@ -108,7 +110,21 @@ module Mail
|
|
108
110
|
end
|
109
111
|
|
110
112
|
smtp = Net::SMTP.new(settings[:address], settings[:port])
|
111
|
-
if settings[:
|
113
|
+
if settings[:tls] || settings[:ssl]
|
114
|
+
if smtp.respond_to?(:enable_tls)
|
115
|
+
unless settings[:openssl_verify_mode]
|
116
|
+
smtp.enable_tls
|
117
|
+
else
|
118
|
+
openssl_verify_mode = settings[:openssl_verify_mode]
|
119
|
+
if openssl_verify_mode.kind_of?(String)
|
120
|
+
openssl_verify_mode = "OpenSSL::SSL::VERIFY_#{openssl_verify_mode.upcase}".constantize
|
121
|
+
end
|
122
|
+
context = Net::SMTP.default_ssl_context
|
123
|
+
context.verify_mode = openssl_verify_mode
|
124
|
+
smtp.enable_tls(context)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
elsif settings[:enable_starttls_auto]
|
112
128
|
if smtp.respond_to?(:enable_starttls_auto)
|
113
129
|
unless settings[:openssl_verify_mode]
|
114
130
|
smtp.enable_starttls_auto
|
@@ -125,8 +141,8 @@ module Mail
|
|
125
141
|
end
|
126
142
|
|
127
143
|
response = nil
|
128
|
-
smtp.start(settings[:domain], settings[:user_name], settings[:password], settings[:authentication]) do |
|
129
|
-
response =
|
144
|
+
smtp.start(settings[:domain], settings[:user_name], settings[:password], settings[:authentication]) do |smtp_obj|
|
145
|
+
response = smtp_obj.sendmail(message, envelope_from, destinations)
|
130
146
|
end
|
131
147
|
|
132
148
|
return settings[:return_response] ? response : self
|
@@ -54,6 +54,8 @@ module Mail
|
|
54
54
|
# order: order of emails returned. Possible values are :asc or :desc. Default value is :asc.
|
55
55
|
# count: number of emails to retrieve. The default value is 10. A value of 1 returns an
|
56
56
|
# instance of Message, not an array of Message instances.
|
57
|
+
# ready_only: will ensure that no writes are made to the inbox during the session.
|
58
|
+
# This is helpful when you don't want your messages to be set to read automatically. Default is false.
|
57
59
|
# delete_after_find: flag for whether to delete each retreived email after find. Default
|
58
60
|
# is false. Use #find_and_delete if you would like this to default to true.
|
59
61
|
#
|
@@ -61,7 +63,7 @@ module Mail
|
|
61
63
|
options = validate_options(options)
|
62
64
|
|
63
65
|
start do |imap|
|
64
|
-
imap.select(options[:mailbox])
|
66
|
+
options[:read_only] ? imap.select(options[:mailbox]) : imap.examine(options[:mailbox])
|
65
67
|
|
66
68
|
message_ids = imap.uid_search(options[:keys])
|
67
69
|
message_ids.reverse! if options[:what].to_sym == :last
|
@@ -101,7 +103,6 @@ module Mail
|
|
101
103
|
mailbox = Net::IMAP.encode_utf7(mailbox)
|
102
104
|
|
103
105
|
start do |imap|
|
104
|
-
imap.select(mailbox)
|
105
106
|
imap.uid_search(['ALL']).each do |message_id|
|
106
107
|
imap.uid_store(message_id, "+FLAGS", [Net::IMAP::DELETED])
|
107
108
|
end
|
@@ -130,6 +131,7 @@ module Mail
|
|
130
131
|
options[:keys] ||= 'ALL'
|
131
132
|
options[:delete_after_find] ||= false
|
132
133
|
options[:mailbox] = Net::IMAP.encode_utf7(options[:mailbox])
|
134
|
+
options[:read_only] ||= false
|
133
135
|
|
134
136
|
options
|
135
137
|
end
|
@@ -257,8 +257,19 @@ module Mail
|
|
257
257
|
if r3
|
258
258
|
r1 = r3
|
259
259
|
else
|
260
|
-
@
|
261
|
-
|
260
|
+
if has_terminal?("@", false, index)
|
261
|
+
r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
|
262
|
+
@index += 1
|
263
|
+
else
|
264
|
+
terminal_parse_failure("@")
|
265
|
+
r4 = nil
|
266
|
+
end
|
267
|
+
if r4
|
268
|
+
r1 = r4
|
269
|
+
else
|
270
|
+
@index = i1
|
271
|
+
r1 = nil
|
272
|
+
end
|
262
273
|
end
|
263
274
|
end
|
264
275
|
if r1
|
@@ -3754,4 +3765,4 @@ module Mail
|
|
3754
3765
|
include RFC2822Obsolete
|
3755
3766
|
end
|
3756
3767
|
|
3757
|
-
end
|
3768
|
+
end
|
data/lib/mail/parts_list.rb
CHANGED
@@ -26,6 +26,10 @@ module Mail
|
|
26
26
|
raise NoMethodError, "#collect! is not defined, please call #collect and create a new PartsList"
|
27
27
|
end
|
28
28
|
|
29
|
+
def sort
|
30
|
+
self.class.new(super)
|
31
|
+
end
|
32
|
+
|
29
33
|
def sort!(order)
|
30
34
|
sorted = self.sort do |a, b|
|
31
35
|
# OK, 10000 is arbitrary... if anyone actually wants to explicitly sort 10000 parts of a
|
data/lib/mail/utilities.rb
CHANGED
@@ -116,11 +116,15 @@ module Mail
|
|
116
116
|
end
|
117
117
|
|
118
118
|
def uri_escape( str )
|
119
|
-
|
119
|
+
uri_parser.escape(str)
|
120
120
|
end
|
121
121
|
|
122
122
|
def uri_unescape( str )
|
123
|
-
|
123
|
+
uri_parser.unescape(str)
|
124
|
+
end
|
125
|
+
|
126
|
+
def uri_parser
|
127
|
+
@uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
|
124
128
|
end
|
125
129
|
|
126
130
|
# Matches two objects with their to_s values case insensitively
|