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.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +140 -1
  3. data/CONTRIBUTING.md +4 -4
  4. data/Gemfile +14 -8
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +24 -23
  7. data/Rakefile +3 -22
  8. data/lib/VERSION +2 -2
  9. data/lib/load_parsers.rb +35 -0
  10. data/lib/mail/attachments_list.rb +2 -2
  11. data/lib/mail/body.rb +5 -5
  12. data/lib/mail/check_delivery_params.rb +57 -0
  13. data/lib/mail/configuration.rb +1 -1
  14. data/lib/mail/core_extensions/nil.rb +4 -2
  15. data/lib/mail/core_extensions/object.rb +8 -8
  16. data/lib/mail/core_extensions/smtp.rb +12 -13
  17. data/lib/mail/core_extensions/string.rb +4 -4
  18. data/lib/mail/elements/address.rb +13 -5
  19. data/lib/mail/elements/envelope_from_element.rb +15 -2
  20. data/lib/mail/elements.rb +12 -12
  21. data/lib/mail/encodings/quoted_printable.rb +4 -3
  22. data/lib/mail/encodings.rb +66 -35
  23. data/lib/mail/field.rb +76 -99
  24. data/lib/mail/fields/bcc_field.rb +2 -2
  25. data/lib/mail/fields/cc_field.rb +2 -2
  26. data/lib/mail/fields/comments_field.rb +1 -1
  27. data/lib/mail/fields/common/common_address.rb +19 -4
  28. data/lib/mail/fields/common/common_field.rb +8 -2
  29. data/lib/mail/fields/common/common_message_id.rb +9 -5
  30. data/lib/mail/fields/content_disposition_field.rb +1 -0
  31. data/lib/mail/fields/content_id_field.rb +1 -2
  32. data/lib/mail/fields/content_transfer_encoding_field.rb +2 -2
  33. data/lib/mail/fields/content_type_field.rb +5 -2
  34. data/lib/mail/fields/date_field.rb +14 -14
  35. data/lib/mail/fields/from_field.rb +2 -2
  36. data/lib/mail/fields/in_reply_to_field.rb +2 -1
  37. data/lib/mail/fields/keywords_field.rb +1 -1
  38. data/lib/mail/fields/message_id_field.rb +2 -3
  39. data/lib/mail/fields/references_field.rb +2 -1
  40. data/lib/mail/fields/reply_to_field.rb +2 -2
  41. data/lib/mail/fields/resent_bcc_field.rb +2 -2
  42. data/lib/mail/fields/resent_cc_field.rb +2 -2
  43. data/lib/mail/fields/resent_from_field.rb +2 -2
  44. data/lib/mail/fields/resent_sender_field.rb +2 -2
  45. data/lib/mail/fields/resent_to_field.rb +2 -2
  46. data/lib/mail/fields/sender_field.rb +7 -7
  47. data/lib/mail/fields/to_field.rb +2 -2
  48. data/lib/mail/fields/unstructured_field.rb +34 -27
  49. data/lib/mail/fields.rb +32 -32
  50. data/lib/mail/header.rb +37 -14
  51. data/lib/mail/message.rb +140 -45
  52. data/lib/mail/multibyte/chars.rb +4 -4
  53. data/lib/mail/multibyte/unicode.rb +8 -0
  54. data/lib/mail/network/delivery_methods/exim.rb +6 -11
  55. data/lib/mail/network/delivery_methods/file_delivery.rb +7 -6
  56. data/lib/mail/network/delivery_methods/sendmail.rb +40 -11
  57. data/lib/mail/network/delivery_methods/smtp.rb +33 -47
  58. data/lib/mail/network/delivery_methods/smtp_connection.rb +7 -24
  59. data/lib/mail/network/delivery_methods/test_mailer.rb +9 -8
  60. data/lib/mail/network/retriever_methods/imap.rb +14 -6
  61. data/lib/mail/network/retriever_methods/pop3.rb +2 -2
  62. data/lib/mail/network/retriever_methods/test_retriever.rb +11 -15
  63. data/lib/mail/network.rb +9 -9
  64. data/lib/mail/parsers/content_transfer_encoding.rb +81 -42
  65. data/lib/mail/parsers/content_transfer_encoding.treetop +4 -6
  66. data/lib/mail/parsers/content_type.rb +16 -12
  67. data/lib/mail/parsers/content_type.treetop +2 -2
  68. data/lib/mail/parsers/rfc2045.rb +12 -55
  69. data/lib/mail/parsers/rfc2045.treetop +1 -2
  70. data/lib/mail/parsers/rfc2822.rb +127 -71
  71. data/lib/mail/parsers/rfc2822.treetop +22 -24
  72. data/lib/mail/part.rb +6 -2
  73. data/lib/mail/parts_list.rb +1 -1
  74. data/lib/mail/patterns.rb +1 -1
  75. data/lib/mail/utilities.rb +25 -17
  76. data/lib/mail/values/unicode_tables.dat +0 -0
  77. data/lib/mail/version_specific/ruby_1_8.rb +23 -2
  78. data/lib/mail/version_specific/ruby_1_9.rb +55 -21
  79. data/lib/mail.rb +18 -18
  80. metadata +89 -37
  81. data/Gemfile.lock +0 -36
  82. 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 it's component parts and allowing you to get the
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 = nil
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 it's best
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
- else
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
- def to_s(*args)
30
- "#{@info}; #{@date_time.to_s(*args)}"
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
- autoload :Address, 'mail/elements/address'
3
- autoload :AddressList, 'mail/elements/address_list'
4
- autoload :ContentDispositionElement, 'mail/elements/content_disposition_element'
5
- autoload :ContentLocationElement, 'mail/elements/content_location_element'
6
- autoload :ContentTransferEncodingElement, 'mail/elements/content_transfer_encoding_element'
7
- autoload :ContentTypeElement, 'mail/elements/content_type_element'
8
- autoload :DateTimeElement, 'mail/elements/date_time_element'
9
- autoload :EnvelopeFromElement, 'mail/elements/envelope_from_element'
10
- autoload :MessageIdsElement, 'mail/elements/message_ids_element'
11
- autoload :MimeVersionElement, 'mail/elements/mime_version_element'
12
- autoload :PhraseList, 'mail/elements/phrase_list'
13
- autoload :ReceivedElement, 'mail/elements/received_element'
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").gsub(/\n/, "\r\n")
22
+ [str.to_lf].pack("M").to_crlf
22
23
  end
23
24
 
24
25
  def self.cost(str)
@@ -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.do
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.index("=?")
117
+ return str unless str =~ /\=\?[^?]+\?[QB]\?[^?]+?\?\=/xmi
118
118
 
119
- str = str.gsub(/\?=(\s*)=\?/, '?==?') # Remove whitespaces between 'encoded-word's
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
- str.split(/([ \t])/).map do |text|
123
- if text.index('=?') .nil?
124
- text
125
- else
126
- # Join QP encoded-words that are adjacent to avoid decoding partial chars
127
- text.gsub!(/\?\=\=\?.+?\?[Qq]\?/m, '') if text =~ /\?==\?/
128
-
129
- # Search for occurences of quoted strings or plain strings
130
- text.scan(/( # Group around entire regex to include it in matches
131
- \=\?[^?]+\?([QB])\?[^?]+?\?\= # Quoted String with subgroup for encoding method
132
- | # or
133
- .+?(?=\=\?|$) # Plain String
134
- )/xmi).map do |matches|
135
- string, method = *matches
136
- if method == 'b' || method == 'B'
137
- b_value_decode(string)
138
- elsif method == 'q' || method == 'Q'
139
- q_value_decode(string)
140
- else
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
- original_encoding = split_encoding_from_string( str )
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 original_encoding.to_s.downcase.gsub("-", "") == to_encoding.to_s.downcase.gsub("-", "")
151
+ if 'utf8' == to_encoding.to_s.downcase.gsub("-", "")
155
152
  output
156
- elsif original_encoding && to_encoding
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, original_encoding, output).first
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!(/(".*?[^#{us_ascii}].*?")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
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
- self_order = FIELD_ORDER.rindex(self.name.to_s.downcase) || 100
128
- other_order = FIELD_ORDER.rindex(other.name.to_s.downcase) || 100
129
- self_order <=> other_order
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(/^(#{FIELD_NAME})\s*:\s*(#{FIELD_BODY})?$/)
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 ignorning) '#{raw_field}'"
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
- # Could do this with constantize and make it "as DRY as", but a simple case
166
- # statement is, well, simpler...
167
- case name.to_s.downcase
168
- when /^to$/i
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 it's field type. This includes all Mail::CommonAddress
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 #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
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
@@ -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 it's field type. This includes all Mail::CommonAddress
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 #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
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 it's field type.
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
- groups.map { |k,v| v.map { |a| a.format } }.flatten
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
- parse((formatted + [val]).join(", "))
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| group_addresses.include?(a.encoded) }.compact.map { |a| a.encoded }
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| group_addresses.include?(a.decoded) }.map { |a| a.decoded }
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(" ")