mail 2.4.4 → 2.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.rdoc +140 -1
- data/CONTRIBUTING.md +4 -4
- data/Gemfile +14 -8
- data/MIT-LICENSE +20 -0
- data/README.md +24 -23
- data/Rakefile +3 -22
- data/lib/VERSION +2 -2
- data/lib/load_parsers.rb +35 -0
- data/lib/mail/attachments_list.rb +2 -2
- data/lib/mail/body.rb +5 -5
- data/lib/mail/check_delivery_params.rb +57 -0
- data/lib/mail/configuration.rb +1 -1
- data/lib/mail/core_extensions/nil.rb +4 -2
- data/lib/mail/core_extensions/object.rb +8 -8
- data/lib/mail/core_extensions/smtp.rb +12 -13
- data/lib/mail/core_extensions/string.rb +4 -4
- data/lib/mail/elements/address.rb +13 -5
- data/lib/mail/elements/envelope_from_element.rb +15 -2
- data/lib/mail/elements.rb +12 -12
- data/lib/mail/encodings/quoted_printable.rb +4 -3
- data/lib/mail/encodings.rb +66 -35
- data/lib/mail/field.rb +76 -99
- data/lib/mail/fields/bcc_field.rb +2 -2
- data/lib/mail/fields/cc_field.rb +2 -2
- data/lib/mail/fields/comments_field.rb +1 -1
- data/lib/mail/fields/common/common_address.rb +19 -4
- data/lib/mail/fields/common/common_field.rb +8 -2
- data/lib/mail/fields/common/common_message_id.rb +9 -5
- data/lib/mail/fields/content_disposition_field.rb +1 -0
- data/lib/mail/fields/content_id_field.rb +1 -2
- data/lib/mail/fields/content_transfer_encoding_field.rb +2 -2
- data/lib/mail/fields/content_type_field.rb +5 -2
- data/lib/mail/fields/date_field.rb +14 -14
- data/lib/mail/fields/from_field.rb +2 -2
- data/lib/mail/fields/in_reply_to_field.rb +2 -1
- data/lib/mail/fields/keywords_field.rb +1 -1
- data/lib/mail/fields/message_id_field.rb +2 -3
- data/lib/mail/fields/references_field.rb +2 -1
- data/lib/mail/fields/reply_to_field.rb +2 -2
- data/lib/mail/fields/resent_bcc_field.rb +2 -2
- data/lib/mail/fields/resent_cc_field.rb +2 -2
- data/lib/mail/fields/resent_from_field.rb +2 -2
- data/lib/mail/fields/resent_sender_field.rb +2 -2
- data/lib/mail/fields/resent_to_field.rb +2 -2
- data/lib/mail/fields/sender_field.rb +7 -7
- data/lib/mail/fields/to_field.rb +2 -2
- data/lib/mail/fields/unstructured_field.rb +34 -27
- data/lib/mail/fields.rb +32 -32
- data/lib/mail/header.rb +37 -14
- data/lib/mail/message.rb +140 -45
- data/lib/mail/multibyte/chars.rb +4 -4
- data/lib/mail/multibyte/unicode.rb +8 -0
- data/lib/mail/network/delivery_methods/exim.rb +6 -11
- data/lib/mail/network/delivery_methods/file_delivery.rb +7 -6
- data/lib/mail/network/delivery_methods/sendmail.rb +40 -11
- data/lib/mail/network/delivery_methods/smtp.rb +33 -47
- data/lib/mail/network/delivery_methods/smtp_connection.rb +7 -24
- data/lib/mail/network/delivery_methods/test_mailer.rb +9 -8
- data/lib/mail/network/retriever_methods/imap.rb +14 -6
- data/lib/mail/network/retriever_methods/pop3.rb +2 -2
- data/lib/mail/network/retriever_methods/test_retriever.rb +11 -15
- data/lib/mail/network.rb +9 -9
- data/lib/mail/parsers/content_transfer_encoding.rb +81 -42
- data/lib/mail/parsers/content_transfer_encoding.treetop +4 -6
- data/lib/mail/parsers/content_type.rb +16 -12
- data/lib/mail/parsers/content_type.treetop +2 -2
- data/lib/mail/parsers/rfc2045.rb +12 -55
- data/lib/mail/parsers/rfc2045.treetop +1 -2
- data/lib/mail/parsers/rfc2822.rb +127 -71
- data/lib/mail/parsers/rfc2822.treetop +22 -24
- data/lib/mail/part.rb +6 -2
- data/lib/mail/parts_list.rb +1 -1
- data/lib/mail/patterns.rb +1 -1
- data/lib/mail/utilities.rb +25 -17
- data/lib/mail/values/unicode_tables.dat +0 -0
- data/lib/mail/version_specific/ruby_1_8.rb +23 -2
- data/lib/mail/version_specific/ruby_1_9.rb +55 -21
- data/lib/mail.rb +18 -18
- metadata +89 -37
- data/Gemfile.lock +0 -36
- data/lib/mail/core_extensions/shell_escape.rb +0 -56
@@ -5,7 +5,7 @@ module Mail
|
|
5
5
|
include Mail::Utilities
|
6
6
|
|
7
7
|
# Mail::Address handles all email addresses in Mail. It takes an email address string
|
8
|
-
# and parses it, breaking it down into
|
8
|
+
# and parses it, breaking it down into its component parts and allowing you to get the
|
9
9
|
# address, comments, display name, name, local part, domain part and fully formatted
|
10
10
|
# address.
|
11
11
|
#
|
@@ -21,7 +21,7 @@ module Mail
|
|
21
21
|
# a.comments #=> ['My email address']
|
22
22
|
# a.to_s #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
|
23
23
|
def initialize(value = nil)
|
24
|
-
@output_type =
|
24
|
+
@output_type = :decode
|
25
25
|
@tree = nil
|
26
26
|
@raw_text = value
|
27
27
|
case
|
@@ -40,7 +40,7 @@ module Mail
|
|
40
40
|
end
|
41
41
|
|
42
42
|
# Returns a correctly formatted address for the email going out. If given
|
43
|
-
# an incorrectly formatted address as input, Mail::Address will do
|
43
|
+
# an incorrectly formatted address as input, Mail::Address will do its best
|
44
44
|
# to format it correctly. This includes quoting display names as needed and
|
45
45
|
# putting the address in angle brackets etc.
|
46
46
|
#
|
@@ -53,8 +53,10 @@ module Mail
|
|
53
53
|
''
|
54
54
|
when display_name
|
55
55
|
[quote_phrase(display_name), "<#{address}>", format_comments].compact.join(" ")
|
56
|
-
|
56
|
+
when address
|
57
57
|
[address, format_comments].compact.join(" ")
|
58
|
+
else
|
59
|
+
tree.text_value
|
58
60
|
end
|
59
61
|
end
|
60
62
|
|
@@ -296,6 +298,12 @@ module Mail
|
|
296
298
|
tree.angle_addr.addr_spec.local_part.text_value
|
297
299
|
when tree.respond_to?(:addr_spec) && tree.addr_spec.respond_to?(:local_part)
|
298
300
|
tree.addr_spec.local_part.text_value
|
301
|
+
when tree.respond_to?(:angle_addr) && tree.angle_addr.respond_to?(:addr_spec) && tree.angle_addr.addr_spec.respond_to?(:local_dot_atom_text)
|
302
|
+
# Ignore local dot atom text when in angle brackets
|
303
|
+
nil
|
304
|
+
when tree.respond_to?(:addr_spec) && tree.addr_spec.respond_to?(:local_dot_atom_text)
|
305
|
+
# Ignore local dot atom text when in angle brackets
|
306
|
+
nil
|
299
307
|
else
|
300
308
|
tree && tree.respond_to?(:local_part) ? tree.local_part.text_value : nil
|
301
309
|
end
|
@@ -303,4 +311,4 @@ module Mail
|
|
303
311
|
|
304
312
|
|
305
313
|
end
|
306
|
-
end
|
314
|
+
end
|
@@ -26,8 +26,21 @@ module Mail
|
|
26
26
|
@address
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
29
|
+
# RFC 4155:
|
30
|
+
# a timestamp indicating the UTC date and time when the message
|
31
|
+
# was originally received, conformant with the syntax of the
|
32
|
+
# traditional UNIX 'ctime' output sans timezone (note that the
|
33
|
+
# use of UTC precludes the need for a timezone indicator);
|
34
|
+
def formatted_date_time
|
35
|
+
if @date_time.respond_to?(:ctime)
|
36
|
+
@date_time.ctime
|
37
|
+
else
|
38
|
+
@date_time.strftime '%a %b %e %T %Y'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
"#{@address} #{formatted_date_time}"
|
31
44
|
end
|
32
45
|
|
33
46
|
end
|
data/lib/mail/elements.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
module Mail
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
2
|
+
register_autoload :Address, 'mail/elements/address'
|
3
|
+
register_autoload :AddressList, 'mail/elements/address_list'
|
4
|
+
register_autoload :ContentDispositionElement, 'mail/elements/content_disposition_element'
|
5
|
+
register_autoload :ContentLocationElement, 'mail/elements/content_location_element'
|
6
|
+
register_autoload :ContentTransferEncodingElement, 'mail/elements/content_transfer_encoding_element'
|
7
|
+
register_autoload :ContentTypeElement, 'mail/elements/content_type_element'
|
8
|
+
register_autoload :DateTimeElement, 'mail/elements/date_time_element'
|
9
|
+
register_autoload :EnvelopeFromElement, 'mail/elements/envelope_from_element'
|
10
|
+
register_autoload :MessageIdsElement, 'mail/elements/message_ids_element'
|
11
|
+
register_autoload :MimeVersionElement, 'mail/elements/mime_version_element'
|
12
|
+
register_autoload :PhraseList, 'mail/elements/phrase_list'
|
13
|
+
register_autoload :ReceivedElement, 'mail/elements/received_element'
|
14
14
|
end
|
@@ -12,13 +12,14 @@ module Mail
|
|
12
12
|
EightBit.can_encode? str
|
13
13
|
end
|
14
14
|
|
15
|
-
# Decode the string from Quoted-Printable
|
15
|
+
# Decode the string from Quoted-Printable. Cope with hard line breaks
|
16
|
+
# that were incorrectly encoded as hex instead of literal CRLF.
|
16
17
|
def self.decode(str)
|
17
|
-
str.unpack("M*").first
|
18
|
+
str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first.to_lf
|
18
19
|
end
|
19
20
|
|
20
21
|
def self.encode(str)
|
21
|
-
[str].pack("M").
|
22
|
+
[str.to_lf].pack("M").to_crlf
|
22
23
|
end
|
23
24
|
|
24
25
|
def self.cost(str)
|
data/lib/mail/encodings.rb
CHANGED
@@ -53,7 +53,7 @@ module Mail
|
|
53
53
|
# Encodes a parameter value using URI Escaping, note the language field 'en' can
|
54
54
|
# be set using Mail::Configuration, like so:
|
55
55
|
#
|
56
|
-
# Mail.defaults
|
56
|
+
# Mail.defaults do
|
57
57
|
# param_encode_language 'jp'
|
58
58
|
# end
|
59
59
|
#
|
@@ -114,52 +114,49 @@ module Mail
|
|
114
114
|
# String has to be of the format =?<encoding>?[QB]?<string>?=
|
115
115
|
def Encodings.value_decode(str)
|
116
116
|
# Optimization: If there's no encoded-words in the string, just return it
|
117
|
-
return str unless str
|
117
|
+
return str unless str =~ /\=\?[^?]+\?[QB]\?[^?]+?\?\=/xmi
|
118
118
|
|
119
|
-
|
119
|
+
lines = collapse_adjacent_encodings(str)
|
120
120
|
|
121
121
|
# Split on white-space boundaries with capture, so we capture the white-space as well
|
122
|
-
|
123
|
-
|
124
|
-
text
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
string
|
122
|
+
lines.map do |line|
|
123
|
+
line.split(/([ \t])/).map do |text|
|
124
|
+
if text.index('=?').nil?
|
125
|
+
text
|
126
|
+
else
|
127
|
+
# Search for occurences of quoted strings or plain strings
|
128
|
+
text.scan(/( # Group around entire regex to include it in matches
|
129
|
+
\=\?[^?]+\?([QB])\?[^?]+?\?\= # Quoted String with subgroup for encoding method
|
130
|
+
| # or
|
131
|
+
.+?(?=\=\?|$) # Plain String
|
132
|
+
)/xmi).map do |matches|
|
133
|
+
string, method = *matches
|
134
|
+
if method == 'b' || method == 'B'
|
135
|
+
b_value_decode(string)
|
136
|
+
elsif method == 'q' || method == 'Q'
|
137
|
+
q_value_decode(string)
|
138
|
+
else
|
139
|
+
string
|
140
|
+
end
|
142
141
|
end
|
143
142
|
end
|
144
143
|
end
|
145
|
-
end.join("")
|
144
|
+
end.flatten.join("")
|
146
145
|
end
|
147
146
|
|
148
147
|
# Takes an encoded string of the format =?<encoding>?[QB]?<string>?=
|
149
148
|
def Encodings.unquote_and_convert_to(str, to_encoding)
|
150
|
-
|
151
|
-
|
152
|
-
output = value_decode( str ).to_s
|
149
|
+
output = value_decode( str ).to_s # output is already converted to UTF-8
|
153
150
|
|
154
|
-
if
|
151
|
+
if 'utf8' == to_encoding.to_s.downcase.gsub("-", "")
|
155
152
|
output
|
156
|
-
elsif
|
153
|
+
elsif to_encoding
|
157
154
|
begin
|
158
155
|
if RUBY_VERSION >= '1.9'
|
159
156
|
output.encode(to_encoding)
|
160
157
|
else
|
161
158
|
require 'iconv'
|
162
|
-
Iconv.iconv(to_encoding,
|
159
|
+
Iconv.iconv(to_encoding, 'UTF-8', output).first
|
163
160
|
end
|
164
161
|
rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
|
165
162
|
# the 'from' parameter specifies a charset other than what the text
|
@@ -178,10 +175,10 @@ module Mail
|
|
178
175
|
def Encodings.address_encode(address, charset = 'utf-8')
|
179
176
|
if address.is_a?(Array)
|
180
177
|
# loop back through for each element
|
181
|
-
address.map { |a| Encodings.address_encode(a, charset) }.join(", ")
|
178
|
+
address.compact.map { |a| Encodings.address_encode(a, charset) }.join(", ")
|
182
179
|
else
|
183
180
|
# find any word boundary that is not ascii and encode it
|
184
|
-
encode_non_usascii(address, charset)
|
181
|
+
encode_non_usascii(address, charset) if address
|
185
182
|
end
|
186
183
|
end
|
187
184
|
|
@@ -189,15 +186,15 @@ module Mail
|
|
189
186
|
return address if address.ascii_only? or charset.nil?
|
190
187
|
us_ascii = %Q{\x00-\x7f}
|
191
188
|
# Encode any non usascii strings embedded inside of quotes
|
192
|
-
address.gsub
|
189
|
+
address = address.gsub(/(".*?[^#{us_ascii}].*?")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
|
193
190
|
# Then loop through all remaining items and encode as needed
|
194
191
|
tokens = address.split(/\s/)
|
195
192
|
map_with_index(tokens) do |word, i|
|
196
193
|
if word.ascii_only?
|
197
194
|
word
|
198
195
|
else
|
199
|
-
previous_non_ascii = tokens[i-1] && !tokens[i-1].ascii_only?
|
200
|
-
if previous_non_ascii
|
196
|
+
previous_non_ascii = i>0 && tokens[i-1] && !tokens[i-1].ascii_only?
|
197
|
+
if previous_non_ascii #why are we adding an extra space here?
|
201
198
|
word = " #{word}"
|
202
199
|
end
|
203
200
|
Encodings.b_value_encode(word, charset)
|
@@ -270,5 +267,39 @@ module Mail
|
|
270
267
|
def Encodings.find_encoding(str)
|
271
268
|
RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
|
272
269
|
end
|
270
|
+
|
271
|
+
# Gets the encoding type (Q or B) from the string.
|
272
|
+
def Encodings.split_value_encoding_from_string(str)
|
273
|
+
match = str.match(/\=\?[^?]+?\?([QB])\?(.+)?\?\=/mi)
|
274
|
+
if match
|
275
|
+
match[1]
|
276
|
+
else
|
277
|
+
nil
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# When the encoded string consists of multiple lines, lines with the same
|
282
|
+
# encoding (Q or B) can be joined together.
|
283
|
+
#
|
284
|
+
# String has to be of the format =?<encoding>?[QB]?<string>?=
|
285
|
+
def Encodings.collapse_adjacent_encodings(str)
|
286
|
+
lines = str.split(/(\?=)\s*(=\?)/).each_slice(2).map(&:join)
|
287
|
+
results = []
|
288
|
+
previous_encoding = nil
|
289
|
+
|
290
|
+
lines.each do |line|
|
291
|
+
encoding = split_value_encoding_from_string(line)
|
292
|
+
|
293
|
+
if encoding == previous_encoding
|
294
|
+
line = results.pop + line
|
295
|
+
line.gsub!(/\?\=\=\?.+?\?[QqBb]\?/m, '')
|
296
|
+
end
|
297
|
+
|
298
|
+
previous_encoding = encoding
|
299
|
+
results << line
|
300
|
+
end
|
301
|
+
|
302
|
+
results
|
303
|
+
end
|
273
304
|
end
|
274
305
|
end
|
data/lib/mail/field.rb
CHANGED
@@ -5,11 +5,11 @@ module Mail
|
|
5
5
|
# Provides a single class to call to create a new structured or unstructured
|
6
6
|
# field. Works out per RFC what field of field it is being given and returns
|
7
7
|
# the correct field of class back on new.
|
8
|
-
#
|
8
|
+
#
|
9
9
|
# ===Per RFC 2822
|
10
|
-
#
|
10
|
+
#
|
11
11
|
# 2.2. Header Fields
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# Header fields are lines composed of a field name, followed by a colon
|
14
14
|
# (":"), followed by a field body, and terminated by CRLF. A field
|
15
15
|
# name MUST be composed of printable US-ASCII characters (i.e.,
|
@@ -19,12 +19,12 @@ module Mail
|
|
19
19
|
# used in header "folding" and "unfolding" as described in section
|
20
20
|
# 2.2.3. All field bodies MUST conform to the syntax described in
|
21
21
|
# sections 3 and 4 of this standard.
|
22
|
-
#
|
22
|
+
#
|
23
23
|
class Field
|
24
|
-
|
24
|
+
|
25
25
|
include Patterns
|
26
26
|
include Comparable
|
27
|
-
|
27
|
+
|
28
28
|
STRUCTURED_FIELDS = %w[ bcc cc content-description content-disposition
|
29
29
|
content-id content-location content-transfer-encoding
|
30
30
|
content-type date from in-reply-to keywords message-id
|
@@ -34,7 +34,39 @@ module Mail
|
|
34
34
|
return-path sender to ]
|
35
35
|
|
36
36
|
KNOWN_FIELDS = STRUCTURED_FIELDS + ['comments', 'subject']
|
37
|
-
|
37
|
+
|
38
|
+
FIELDS_MAP = {
|
39
|
+
"to" => ToField,
|
40
|
+
"cc" => CcField,
|
41
|
+
"bcc" => BccField,
|
42
|
+
"message-id" => MessageIdField,
|
43
|
+
"in-reply-to" => InReplyToField,
|
44
|
+
"references" => ReferencesField,
|
45
|
+
"subject" => SubjectField,
|
46
|
+
"comments" => CommentsField,
|
47
|
+
"keywords" => KeywordsField,
|
48
|
+
"date" => DateField,
|
49
|
+
"from" => FromField,
|
50
|
+
"sender" => SenderField,
|
51
|
+
"reply-to" => ReplyToField,
|
52
|
+
"resent-date" => ResentDateField,
|
53
|
+
"resent-from" => ResentFromField,
|
54
|
+
"resent-sender" => ResentSenderField,
|
55
|
+
"resent-to" => ResentToField,
|
56
|
+
"resent-cc" => ResentCcField,
|
57
|
+
"resent-bcc" => ResentBccField,
|
58
|
+
"resent-message-id" => ResentMessageIdField,
|
59
|
+
"return-path" => ReturnPathField,
|
60
|
+
"received" => ReceivedField,
|
61
|
+
"mime-version" => MimeVersionField,
|
62
|
+
"content-transfer-encoding" => ContentTransferEncodingField,
|
63
|
+
"content-description" => ContentDescriptionField,
|
64
|
+
"content-disposition" => ContentDispositionField,
|
65
|
+
"content-type" => ContentTypeField,
|
66
|
+
"content-id" => ContentIdField,
|
67
|
+
"content-location" => ContentLocationField,
|
68
|
+
}
|
69
|
+
|
38
70
|
# Generic Field Exception
|
39
71
|
class FieldError < StandardError
|
40
72
|
end
|
@@ -55,25 +87,25 @@ module Mail
|
|
55
87
|
# Raised when attempting to set a structured field's contents to an invalid syntax
|
56
88
|
class SyntaxError < FieldError #:nodoc:
|
57
89
|
end
|
58
|
-
|
90
|
+
|
59
91
|
# Accepts a string:
|
60
|
-
#
|
92
|
+
#
|
61
93
|
# Field.new("field-name: field data")
|
62
|
-
#
|
94
|
+
#
|
63
95
|
# Or name, value pair:
|
64
|
-
#
|
96
|
+
#
|
65
97
|
# Field.new("field-name", "value")
|
66
|
-
#
|
98
|
+
#
|
67
99
|
# Or a name by itself:
|
68
|
-
#
|
100
|
+
#
|
69
101
|
# Field.new("field-name")
|
70
|
-
#
|
102
|
+
#
|
71
103
|
# Note, does not want a terminating carriage return. Returns
|
72
104
|
# self appropriately parsed. If value is not a string, then
|
73
105
|
# it will be passed through as is, for example, content-type
|
74
|
-
# field can accept an array with the type and a hash of
|
106
|
+
# field can accept an array with the type and a hash of
|
75
107
|
# parameters:
|
76
|
-
#
|
108
|
+
#
|
77
109
|
# Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
|
78
110
|
def initialize(name, value = nil, charset = 'utf-8')
|
79
111
|
case
|
@@ -92,47 +124,49 @@ module Mail
|
|
92
124
|
def field=(value)
|
93
125
|
@field = value
|
94
126
|
end
|
95
|
-
|
127
|
+
|
96
128
|
def field
|
97
129
|
@field
|
98
130
|
end
|
99
|
-
|
131
|
+
|
100
132
|
def name
|
101
133
|
field.name
|
102
134
|
end
|
103
|
-
|
135
|
+
|
104
136
|
def value
|
105
137
|
field.value
|
106
138
|
end
|
107
|
-
|
139
|
+
|
108
140
|
def value=(val)
|
109
141
|
create_field(name, val, charset)
|
110
142
|
end
|
111
|
-
|
143
|
+
|
112
144
|
def to_s
|
113
145
|
field.to_s
|
114
146
|
end
|
115
|
-
|
147
|
+
|
116
148
|
def update(name, value)
|
117
149
|
create_field(name, value, charset)
|
118
150
|
end
|
119
|
-
|
151
|
+
|
120
152
|
def same( other )
|
121
153
|
match_to_s(other.name, field.name)
|
122
154
|
end
|
123
|
-
|
155
|
+
|
124
156
|
alias_method :==, :same
|
125
|
-
|
157
|
+
|
126
158
|
def <=>( other )
|
127
|
-
|
128
|
-
|
129
|
-
|
159
|
+
self.field_order_id <=> other.field_order_id
|
160
|
+
end
|
161
|
+
|
162
|
+
def field_order_id
|
163
|
+
@field_order_id ||= (FIELD_ORDER_LOOKUP[self.name.to_s.downcase] || 100)
|
130
164
|
end
|
131
|
-
|
165
|
+
|
132
166
|
def method_missing(name, *args, &block)
|
133
167
|
field.send(name, *args, &block)
|
134
168
|
end
|
135
|
-
|
169
|
+
|
136
170
|
FIELD_ORDER = %w[ return-path received
|
137
171
|
resent-date resent-from resent-sender resent-to
|
138
172
|
resent-cc resent-bcc resent-message-id
|
@@ -141,16 +175,18 @@ module Mail
|
|
141
175
|
subject comments keywords
|
142
176
|
mime-version content-type content-transfer-encoding
|
143
177
|
content-location content-disposition content-description ]
|
144
|
-
|
178
|
+
|
179
|
+
FIELD_ORDER_LOOKUP = Hash[FIELD_ORDER.each_with_index.to_a]
|
180
|
+
|
145
181
|
private
|
146
|
-
|
182
|
+
|
147
183
|
def split(raw_field)
|
148
|
-
match_data = raw_field.mb_chars.match(
|
184
|
+
match_data = raw_field.mb_chars.match(FIELD_SPLIT)
|
149
185
|
[match_data[1].to_s.mb_chars.strip, match_data[2].to_s.mb_chars.strip]
|
150
186
|
rescue
|
151
|
-
STDERR.puts "WARNING: Could not parse (and so
|
187
|
+
STDERR.puts "WARNING: Could not parse (and so ignoring) '#{raw_field}'"
|
152
188
|
end
|
153
|
-
|
189
|
+
|
154
190
|
def create_field(name, value, charset)
|
155
191
|
begin
|
156
192
|
self.field = new_field(name, value, charset)
|
@@ -162,73 +198,14 @@ module Mail
|
|
162
198
|
end
|
163
199
|
|
164
200
|
def new_field(name, value, charset)
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
ToField.new(value, charset)
|
170
|
-
when /^cc$/i
|
171
|
-
CcField.new(value, charset)
|
172
|
-
when /^bcc$/i
|
173
|
-
BccField.new(value, charset)
|
174
|
-
when /^message-id$/i
|
175
|
-
MessageIdField.new(value, charset)
|
176
|
-
when /^in-reply-to$/i
|
177
|
-
InReplyToField.new(value, charset)
|
178
|
-
when /^references$/i
|
179
|
-
ReferencesField.new(value, charset)
|
180
|
-
when /^subject$/i
|
181
|
-
SubjectField.new(value, charset)
|
182
|
-
when /^comments$/i
|
183
|
-
CommentsField.new(value, charset)
|
184
|
-
when /^keywords$/i
|
185
|
-
KeywordsField.new(value, charset)
|
186
|
-
when /^date$/i
|
187
|
-
DateField.new(value, charset)
|
188
|
-
when /^from$/i
|
189
|
-
FromField.new(value, charset)
|
190
|
-
when /^sender$/i
|
191
|
-
SenderField.new(value, charset)
|
192
|
-
when /^reply-to$/i
|
193
|
-
ReplyToField.new(value, charset)
|
194
|
-
when /^resent-date$/i
|
195
|
-
ResentDateField.new(value, charset)
|
196
|
-
when /^resent-from$/i
|
197
|
-
ResentFromField.new(value, charset)
|
198
|
-
when /^resent-sender$/i
|
199
|
-
ResentSenderField.new(value, charset)
|
200
|
-
when /^resent-to$/i
|
201
|
-
ResentToField.new(value, charset)
|
202
|
-
when /^resent-cc$/i
|
203
|
-
ResentCcField.new(value, charset)
|
204
|
-
when /^resent-bcc$/i
|
205
|
-
ResentBccField.new(value, charset)
|
206
|
-
when /^resent-message-id$/i
|
207
|
-
ResentMessageIdField.new(value, charset)
|
208
|
-
when /^return-path$/i
|
209
|
-
ReturnPathField.new(value, charset)
|
210
|
-
when /^received$/i
|
211
|
-
ReceivedField.new(value, charset)
|
212
|
-
when /^mime-version$/i
|
213
|
-
MimeVersionField.new(value, charset)
|
214
|
-
when /^content-transfer-encoding$/i
|
215
|
-
ContentTransferEncodingField.new(value, charset)
|
216
|
-
when /^content-description$/i
|
217
|
-
ContentDescriptionField.new(value, charset)
|
218
|
-
when /^content-disposition$/i
|
219
|
-
ContentDispositionField.new(value, charset)
|
220
|
-
when /^content-type$/i
|
221
|
-
ContentTypeField.new(value, charset)
|
222
|
-
when /^content-id$/i
|
223
|
-
ContentIdField.new(value, charset)
|
224
|
-
when /^content-location$/i
|
225
|
-
ContentLocationField.new(value, charset)
|
226
|
-
else
|
201
|
+
lower_case_name = name.to_s.downcase
|
202
|
+
if field_klass = FIELDS_MAP[lower_case_name]
|
203
|
+
field_klass.new(value, charset)
|
204
|
+
else
|
227
205
|
OptionalField.new(name, value, charset)
|
228
206
|
end
|
229
|
-
|
230
207
|
end
|
231
208
|
|
232
209
|
end
|
233
|
-
|
210
|
+
|
234
211
|
end
|
@@ -6,7 +6,7 @@
|
|
6
6
|
# field in the email.
|
7
7
|
#
|
8
8
|
# Sending bcc to a mail message will instantiate a Mail::Field object that
|
9
|
-
# has a BccField as
|
9
|
+
# has a BccField as its field type. This includes all Mail::CommonAddress
|
10
10
|
# module instance metods.
|
11
11
|
#
|
12
12
|
# Only one Bcc field can appear in a header, though it can have multiple
|
@@ -16,7 +16,7 @@
|
|
16
16
|
#
|
17
17
|
# mail = Mail.new
|
18
18
|
# mail.bcc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
|
19
|
-
# mail.bcc #=> ['
|
19
|
+
# mail.bcc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
|
20
20
|
# mail[:bcc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
|
21
21
|
# mail['bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
|
22
22
|
# mail['Bcc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::BccField:0x180e1c4
|
data/lib/mail/fields/cc_field.rb
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# field in the email.
|
7
7
|
#
|
8
8
|
# Sending cc to a mail message will instantiate a Mail::Field object that
|
9
|
-
# has a CcField as
|
9
|
+
# has a CcField as its field type. This includes all Mail::CommonAddress
|
10
10
|
# module instance metods.
|
11
11
|
#
|
12
12
|
# Only one Cc field can appear in a header, though it can have multiple
|
@@ -16,7 +16,7 @@
|
|
16
16
|
#
|
17
17
|
# mail = Mail.new
|
18
18
|
# mail.cc = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
|
19
|
-
# mail.cc #=> ['
|
19
|
+
# mail.cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
|
20
20
|
# mail[:cc] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
|
21
21
|
# mail['cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
|
22
22
|
# mail['Cc'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::CcField:0x180e1c4
|
@@ -6,7 +6,7 @@
|
|
6
6
|
# header field in the email.
|
7
7
|
#
|
8
8
|
# Sending comments to a mail message will instantiate a Mail::Field object that
|
9
|
-
# has a CommentsField as
|
9
|
+
# has a CommentsField as its field type.
|
10
10
|
#
|
11
11
|
# An email header can have as many comments fields as it wants. There is no upper
|
12
12
|
# limit, the comments field is also optional (that is, no comment is needed)
|
@@ -62,7 +62,17 @@ module Mail
|
|
62
62
|
|
63
63
|
# Returns the addresses that are part of groups
|
64
64
|
def group_addresses
|
65
|
-
|
65
|
+
decoded_group_addresses
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns a list of decoded group addresses
|
69
|
+
def decoded_group_addresses
|
70
|
+
groups.map { |k,v| v.map { |a| a.decoded } }.flatten
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns a list of encoded group addresses
|
74
|
+
def encoded_group_addresses
|
75
|
+
groups.map { |k,v| v.map { |a| a.encoded } }.flatten
|
66
76
|
end
|
67
77
|
|
68
78
|
# Returns the name of all the groups in a string
|
@@ -81,15 +91,20 @@ module Mail
|
|
81
91
|
when val.blank?
|
82
92
|
parse(encoded)
|
83
93
|
else
|
84
|
-
|
94
|
+
self.value = [self.value, val].reject {|a| a.blank? }.join(", ")
|
85
95
|
end
|
86
96
|
end
|
97
|
+
|
98
|
+
def value=(val)
|
99
|
+
super
|
100
|
+
parse(self.value)
|
101
|
+
end
|
87
102
|
|
88
103
|
private
|
89
104
|
|
90
105
|
def do_encode(field_name)
|
91
106
|
return '' if value.blank?
|
92
|
-
address_array = tree.addresses.reject { |a|
|
107
|
+
address_array = tree.addresses.reject { |a| encoded_group_addresses.include?(a.encoded) }.compact.map { |a| a.encoded }
|
93
108
|
address_text = address_array.join(", \r\n\s")
|
94
109
|
group_array = groups.map { |k,v| "#{k}: #{v.map { |a| a.encoded }.join(", \r\n\s")};" }
|
95
110
|
group_text = group_array.join(" \r\n\s")
|
@@ -99,7 +114,7 @@ module Mail
|
|
99
114
|
|
100
115
|
def do_decode
|
101
116
|
return nil if value.blank?
|
102
|
-
address_array = tree.addresses.reject { |a|
|
117
|
+
address_array = tree.addresses.reject { |a| decoded_group_addresses.include?(a.decoded) }.map { |a| a.decoded }
|
103
118
|
address_text = address_array.join(", ")
|
104
119
|
group_array = groups.map { |k,v| "#{k}: #{v.map { |a| a.decoded }.join(", ")};" }
|
105
120
|
group_text = group_array.join(" ")
|