mail 2.7.1 → 2.8.0

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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +45 -28
  3. data/lib/mail/attachments_list.rb +2 -5
  4. data/lib/mail/body.rb +24 -47
  5. data/lib/mail/check_delivery_params.rb +21 -16
  6. data/lib/mail/constants.rb +27 -5
  7. data/lib/mail/elements/address.rb +27 -27
  8. data/lib/mail/elements/address_list.rb +1 -1
  9. data/lib/mail/elements/content_disposition_element.rb +1 -1
  10. data/lib/mail/elements/content_location_element.rb +1 -1
  11. data/lib/mail/elements/content_transfer_encoding_element.rb +1 -1
  12. data/lib/mail/elements/content_type_element.rb +8 -4
  13. data/lib/mail/elements/date_time_element.rb +1 -1
  14. data/lib/mail/elements/envelope_from_element.rb +13 -7
  15. data/lib/mail/elements/message_ids_element.rb +14 -5
  16. data/lib/mail/elements/mime_version_element.rb +1 -1
  17. data/lib/mail/elements/phrase_list.rb +7 -2
  18. data/lib/mail/elements/received_element.rb +20 -6
  19. data/lib/mail/encodings/7bit.rb +5 -0
  20. data/lib/mail/encodings/base64.rb +2 -2
  21. data/lib/mail/encodings/quoted_printable.rb +2 -2
  22. data/lib/mail/encodings.rb +30 -59
  23. data/lib/mail/envelope.rb +11 -14
  24. data/lib/mail/field.rb +37 -53
  25. data/lib/mail/field_list.rb +60 -7
  26. data/lib/mail/fields/bcc_field.rb +34 -52
  27. data/lib/mail/fields/cc_field.rb +28 -49
  28. data/lib/mail/fields/comments_field.rb +27 -37
  29. data/lib/mail/fields/common_address_field.rb +170 -0
  30. data/lib/mail/fields/common_date_field.rb +58 -0
  31. data/lib/mail/fields/common_field.rb +77 -0
  32. data/lib/mail/fields/common_message_id_field.rb +42 -0
  33. data/lib/mail/fields/content_description_field.rb +7 -14
  34. data/lib/mail/fields/content_disposition_field.rb +13 -38
  35. data/lib/mail/fields/content_id_field.rb +24 -51
  36. data/lib/mail/fields/content_location_field.rb +11 -25
  37. data/lib/mail/fields/content_transfer_encoding_field.rb +31 -31
  38. data/lib/mail/fields/content_type_field.rb +46 -71
  39. data/lib/mail/fields/date_field.rb +23 -51
  40. data/lib/mail/fields/from_field.rb +28 -49
  41. data/lib/mail/fields/in_reply_to_field.rb +38 -49
  42. data/lib/mail/fields/keywords_field.rb +18 -31
  43. data/lib/mail/fields/message_id_field.rb +25 -71
  44. data/lib/mail/fields/mime_version_field.rb +19 -30
  45. data/lib/mail/fields/named_structured_field.rb +11 -0
  46. data/lib/mail/fields/named_unstructured_field.rb +11 -0
  47. data/lib/mail/fields/optional_field.rb +5 -6
  48. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +12 -10
  49. data/lib/mail/fields/received_field.rb +43 -57
  50. data/lib/mail/fields/references_field.rb +35 -49
  51. data/lib/mail/fields/reply_to_field.rb +28 -49
  52. data/lib/mail/fields/resent_bcc_field.rb +28 -49
  53. data/lib/mail/fields/resent_cc_field.rb +28 -49
  54. data/lib/mail/fields/resent_date_field.rb +5 -29
  55. data/lib/mail/fields/resent_from_field.rb +28 -49
  56. data/lib/mail/fields/resent_message_id_field.rb +5 -29
  57. data/lib/mail/fields/resent_sender_field.rb +27 -56
  58. data/lib/mail/fields/resent_to_field.rb +28 -49
  59. data/lib/mail/fields/return_path_field.rb +50 -54
  60. data/lib/mail/fields/sender_field.rb +34 -55
  61. data/lib/mail/fields/structured_field.rb +3 -30
  62. data/lib/mail/fields/subject_field.rb +9 -11
  63. data/lib/mail/fields/to_field.rb +28 -49
  64. data/lib/mail/fields/unstructured_field.rb +16 -48
  65. data/lib/mail/header.rb +69 -110
  66. data/lib/mail/matchers/attachment_matchers.rb +15 -0
  67. data/lib/mail/message.rb +52 -66
  68. data/lib/mail/multibyte/chars.rb +8 -166
  69. data/lib/mail/multibyte/utils.rb +26 -43
  70. data/lib/mail/multibyte.rb +1 -11
  71. data/lib/mail/network/delivery_methods/exim.rb +5 -4
  72. data/lib/mail/network/delivery_methods/file_delivery.rb +11 -10
  73. data/lib/mail/network/delivery_methods/logger_delivery.rb +2 -5
  74. data/lib/mail/network/delivery_methods/sendmail.rb +27 -35
  75. data/lib/mail/network/delivery_methods/smtp.rb +25 -9
  76. data/lib/mail/network/delivery_methods/smtp_connection.rb +3 -12
  77. data/lib/mail/network/delivery_methods/test_mailer.rb +4 -2
  78. data/lib/mail/network/retriever_methods/base.rb +8 -8
  79. data/lib/mail/network/retriever_methods/imap.rb +2 -2
  80. data/lib/mail/network/retriever_methods/pop3.rb +2 -2
  81. data/lib/mail/network/retriever_methods/test_retriever.rb +2 -1
  82. data/lib/mail/parsers/address_lists_parser.rb +33070 -33064
  83. data/lib/mail/parsers/address_lists_parser.rl +7 -0
  84. data/lib/mail/parsers/content_disposition_parser.rb +833 -827
  85. data/lib/mail/parsers/content_disposition_parser.rl +7 -0
  86. data/lib/mail/parsers/content_location_parser.rb +770 -764
  87. data/lib/mail/parsers/content_location_parser.rl +7 -0
  88. data/lib/mail/parsers/content_transfer_encoding_parser.rb +474 -468
  89. data/lib/mail/parsers/content_transfer_encoding_parser.rl +7 -0
  90. data/lib/mail/parsers/content_type_parser.rb +971 -965
  91. data/lib/mail/parsers/content_type_parser.rl +7 -0
  92. data/lib/mail/parsers/date_time_parser.rb +838 -832
  93. data/lib/mail/parsers/date_time_parser.rl +7 -0
  94. data/lib/mail/parsers/envelope_from_parser.rb +3623 -3529
  95. data/lib/mail/parsers/envelope_from_parser.rl +7 -0
  96. data/lib/mail/parsers/message_ids_parser.rb +5107 -2800
  97. data/lib/mail/parsers/message_ids_parser.rl +12 -1
  98. data/lib/mail/parsers/mime_version_parser.rb +463 -457
  99. data/lib/mail/parsers/mime_version_parser.rl +7 -0
  100. data/lib/mail/parsers/phrase_lists_parser.rb +836 -830
  101. data/lib/mail/parsers/phrase_lists_parser.rl +8 -1
  102. data/lib/mail/parsers/received_parser.rb +8688 -8682
  103. data/lib/mail/parsers/received_parser.rl +7 -0
  104. data/lib/mail/parsers/rfc5322.rl +28 -13
  105. data/lib/mail/parsers.rb +11 -17
  106. data/lib/mail/part.rb +5 -9
  107. data/lib/mail/parts_list.rb +57 -0
  108. data/lib/mail/smtp_envelope.rb +57 -0
  109. data/lib/mail/utilities.rb +307 -69
  110. data/lib/mail/version.rb +2 -2
  111. data/lib/mail/yaml.rb +30 -0
  112. data/lib/mail.rb +3 -20
  113. metadata +72 -18
  114. data/lib/mail/core_extensions/smtp.rb +0 -28
  115. data/lib/mail/core_extensions/string.rb +0 -17
  116. data/lib/mail/fields/common/address_container.rb +0 -17
  117. data/lib/mail/fields/common/common_address.rb +0 -161
  118. data/lib/mail/fields/common/common_date.rb +0 -36
  119. data/lib/mail/fields/common/common_field.rb +0 -52
  120. data/lib/mail/fields/common/common_message_id.rb +0 -49
  121. data/lib/mail/version_specific/ruby_1_8.rb +0 -163
  122. data/lib/mail/version_specific/ruby_1_9.rb +0 -278
@@ -1,13 +1,18 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
3
  require 'mail/parsers/message_ids_parser'
4
+ require 'mail/utilities'
4
5
 
5
6
  module Mail
6
- class MessageIdsElement
7
+ class MessageIdsElement #:nodoc:
8
+ def self.parse(string)
9
+ new(string).tap(&:message_ids)
10
+ end
11
+
7
12
  attr_reader :message_ids
8
13
 
9
14
  def initialize(string)
10
- @message_ids = Mail::Parsers::MessageIdsParser.parse(string).message_ids.map { |msg_id| clean_msg_id(msg_id) }
15
+ @message_ids = parse(string)
11
16
  end
12
17
 
13
18
  def message_id
@@ -15,8 +20,12 @@ module Mail
15
20
  end
16
21
 
17
22
  private
18
- def clean_msg_id(val)
19
- val =~ /.*<(.*)>.*/ ? $1 : val
20
- end
23
+ def parse(string)
24
+ if Utilities.blank? string
25
+ []
26
+ else
27
+ Mail::Parsers::MessageIdsParser.parse(string).message_ids
28
+ end
29
+ end
21
30
  end
22
31
  end
@@ -3,7 +3,7 @@
3
3
  require 'mail/parsers/mime_version_parser'
4
4
 
5
5
  module Mail
6
- class MimeVersionElement
6
+ class MimeVersionElement #:nodoc:
7
7
  attr_reader :major, :minor
8
8
 
9
9
  def initialize(string)
@@ -4,11 +4,16 @@ require 'mail/parsers/phrase_lists_parser'
4
4
  require 'mail/utilities'
5
5
 
6
6
  module Mail
7
- class PhraseList
7
+ class PhraseList #:nodoc:
8
8
  attr_reader :phrases
9
9
 
10
10
  def initialize(string)
11
- @phrases = Mail::Parsers::PhraseListsParser.parse(string).phrases.map { |p| Mail::Utilities.unquote(p) }
11
+ @phrases =
12
+ if Utilities.blank? string
13
+ []
14
+ else
15
+ Mail::Parsers::PhraseListsParser.parse(string).phrases.map { |p| Mail::Utilities.unquote(p) }
16
+ end
12
17
  end
13
18
  end
14
19
  end
@@ -1,21 +1,35 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
3
  require 'mail/parsers/received_parser'
4
+ require 'mail/utilities'
4
5
  require 'date'
5
6
 
6
7
  module Mail
7
- class ReceivedElement
8
- include Mail::Utilities
9
- attr_reader :date_time, :info
8
+ class ReceivedElement #:nodoc:
9
+ attr_reader :info, :date_time
10
10
 
11
11
  def initialize(string)
12
- received = Mail::Parsers::ReceivedParser.parse(string)
13
- @date_time = ::DateTime.parse("#{received.date} #{received.time}")
14
- @info = received.info
12
+ if Utilities.blank? string
13
+ @date_time = nil
14
+ @info = nil
15
+ else
16
+ received = Mail::Parsers::ReceivedParser.parse(string)
17
+ @date_time = datetime_for(received)
18
+ @info = received.info
19
+ end
15
20
  end
16
21
 
17
22
  def to_s(*args)
18
23
  "#{info}; #{date_time.to_s(*args)}"
19
24
  end
25
+
26
+ private
27
+ def datetime_for(received)
28
+ ::DateTime.parse("#{received.date} #{received.time}")
29
+ rescue ArgumentError => e
30
+ raise e unless e.message == 'invalid date'
31
+ warn "WARNING: Invalid date field for received element (#{received.date} #{received.time}): #{e.class}: #{e.message}"
32
+ nil
33
+ end
20
34
  end
21
35
  end
@@ -17,6 +17,11 @@ module Mail
17
17
  def self.encode(str)
18
18
  ::Mail::Utilities.binary_unsafe_to_crlf str
19
19
  end
20
+
21
+ # Per RFC 2045 2.7. 7bit Data, No octets with decimal values greater than 127 are allowed.
22
+ def self.compatible_input?(str)
23
+ str.ascii_only? && super
24
+ end
20
25
  end
21
26
  end
22
27
  end
@@ -16,11 +16,11 @@ module Mail
16
16
  end
17
17
 
18
18
  def self.decode(str)
19
- RubyVer.decode_base64(str)
19
+ Utilities.decode_base64(str)
20
20
  end
21
21
 
22
22
  def self.encode(str)
23
- ::Mail::Utilities.binary_unsafe_to_crlf(RubyVer.encode_base64(str))
23
+ ::Mail::Utilities.binary_unsafe_to_crlf(Utilities.encode_base64(str))
24
24
  end
25
25
 
26
26
  # 3 bytes in -> 4 bytes out
@@ -16,11 +16,11 @@ module Mail
16
16
  # Decode the string from Quoted-Printable. Cope with hard line breaks
17
17
  # that were incorrectly encoded as hex instead of literal CRLF.
18
18
  def self.decode(str)
19
- str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first
19
+ ::Mail::Utilities.to_lf ::Mail::Utilities.to_crlf(str).gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first
20
20
  end
21
21
 
22
22
  def self.encode(str)
23
- [str].pack("M")
23
+ ::Mail::Utilities.to_crlf [::Mail::Utilities.to_lf(str)].pack("M")
24
24
  end
25
25
 
26
26
  def self.cost(str)
@@ -52,7 +52,7 @@ module Mail
52
52
 
53
53
  def Encodings.transcode_charset(str, from_charset, to_charset = 'UTF-8')
54
54
  if from_charset
55
- RubyVer.transcode_charset str, from_charset, to_charset
55
+ Utilities.transcode_charset str, from_charset, to_charset
56
56
  else
57
57
  str
58
58
  end
@@ -65,8 +65,7 @@ module Mail
65
65
  # param_encode_language 'jp'
66
66
  # end
67
67
  #
68
- # The character set used for encoding will either be the value of $KCODE for
69
- # Ruby < 1.9 or the encoding on the string passed in.
68
+ # The character set used for encoding will be the encoding on the string passed in.
70
69
  #
71
70
  # Example:
72
71
  #
@@ -78,7 +77,7 @@ module Mail
78
77
  when str.ascii_only?
79
78
  str
80
79
  else
81
- RubyVer.param_encode(str)
80
+ Utilities.param_encode(str)
82
81
  end
83
82
  end
84
83
 
@@ -92,15 +91,15 @@ module Mail
92
91
  # str.encoding #=> 'ISO-8859-1' ## Only on Ruby 1.9
93
92
  # str #=> "This is fun"
94
93
  def Encodings.param_decode(str, encoding)
95
- RubyVer.param_decode(str, encoding)
94
+ Utilities.param_decode(str, encoding)
96
95
  end
97
96
 
98
97
  # Decodes or encodes a string as needed for either Base64 or QP encoding types in
99
98
  # the =?<encoding>?[QB]?<string>?=" format.
100
99
  #
101
100
  # The output type needs to be :decode to decode the input string or :encode to
102
- # encode the input string. The character set used for encoding will either be
103
- # the value of $KCODE for Ruby < 1.9 or the encoding on the string passed in.
101
+ # encode the input string. The character set used for encoding will be the
102
+ # encoding on the string passed in.
104
103
  #
105
104
  # On encoding, will only send out Base64 encoded strings.
106
105
  def Encodings.decode_encode(str, output_type)
@@ -111,7 +110,7 @@ module Mail
111
110
  if str.ascii_only?
112
111
  str
113
112
  else
114
- Encodings.b_value_encode(str, find_encoding(str))
113
+ Encodings.b_value_encode(str, str.encoding)
115
114
  end
116
115
  end
117
116
  end
@@ -145,13 +144,8 @@ module Mail
145
144
  output
146
145
  elsif to_encoding
147
146
  begin
148
- if RUBY_VERSION >= '1.9'
149
- output.encode(to_encoding)
150
- else
151
- require 'iconv'
152
- Iconv.iconv(to_encoding, 'UTF-8', output).first
153
- end
154
- rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
147
+ output.encode(to_encoding)
148
+ rescue Errno::EINVAL
155
149
  # the 'from' parameter specifies a charset other than what the text
156
150
  # actually is...not much we can do in this case but just return the
157
151
  # unconverted text.
@@ -176,42 +170,23 @@ module Mail
176
170
  def Encodings.encode_non_usascii(address, charset)
177
171
  return address if address.ascii_only? or charset.nil?
178
172
 
179
- # With KCODE=u we can't use regexps on other encodings. Go ASCII.
180
- with_ascii_kcode do
181
- # Encode all strings embedded inside of quotes
182
- address = address.gsub(/("[^"]*[^\/]")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
183
-
184
- # Then loop through all remaining items and encode as needed
185
- tokens = address.split(/\s/)
186
-
187
- map_with_index(tokens) do |word, i|
188
- if word.ascii_only?
189
- word
190
- else
191
- previous_non_ascii = i>0 && tokens[i-1] && !tokens[i-1].ascii_only?
192
- if previous_non_ascii #why are we adding an extra space here?
193
- word = " #{word}"
194
- end
195
- Encodings.b_value_encode(word, charset)
196
- end
197
- end.join(' ')
198
- end
199
- end
173
+ # Encode all strings embedded inside of quotes
174
+ address = address.gsub(/("[^"]*[^\/]")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
175
+
176
+ # Then loop through all remaining items and encode as needed
177
+ tokens = address.split(/\s/)
200
178
 
201
- if RUBY_VERSION < '1.9'
202
- # With KCODE=u we can't use regexps on other encodings. Go ASCII.
203
- def Encodings.with_ascii_kcode #:nodoc:
204
- if $KCODE
205
- $KCODE, original_kcode = '', $KCODE
179
+ map_with_index(tokens) do |word, i|
180
+ if word.ascii_only?
181
+ word
182
+ else
183
+ previous_non_ascii = i>0 && tokens[i-1] && !tokens[i-1].ascii_only?
184
+ if previous_non_ascii #why are we adding an extra space here?
185
+ word = " #{word}"
186
+ end
187
+ Encodings.b_value_encode(word, charset)
206
188
  end
207
- yield
208
- ensure
209
- $KCODE = original_kcode if original_kcode
210
- end
211
- else
212
- def Encodings.with_ascii_kcode #:nodoc:
213
- yield
214
- end
189
+ end.join(' ')
215
190
  end
216
191
 
217
192
  # Encode a string with Base64 Encoding and returns it ready to be inserted
@@ -226,7 +201,7 @@ module Mail
226
201
  string
227
202
  else
228
203
  Encodings.each_base64_chunk_byterange(string, 60).map do |chunk|
229
- str, encoding = RubyVer.b_value_encode(chunk, encoding)
204
+ str, encoding = Utilities.b_value_encode(chunk, encoding)
230
205
  "=?#{encoding}?B?#{str.chomp}?="
231
206
  end.join(" ")
232
207
  end
@@ -241,7 +216,7 @@ module Mail
241
216
  # #=> "=?UTF-8?Q?This_is_=E3=81=82_string?="
242
217
  def Encodings.q_value_encode(encoded_str, encoding = nil)
243
218
  return encoded_str if encoded_str.to_s.ascii_only?
244
- string, encoding = RubyVer.q_value_encode(encoded_str, encoding)
219
+ string, encoding = Utilities.q_value_encode(encoded_str, encoding)
245
220
  string.gsub!("=\r\n", '') # We already have limited the string to the length we want
246
221
  map_lines(string) do |str|
247
222
  "=?#{encoding}?Q?#{str.chomp.gsub(/ /, '_')}?="
@@ -257,7 +232,7 @@ module Mail
257
232
  # Encodings.b_value_decode("=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=")
258
233
  # #=> 'This is あ string'
259
234
  def Encodings.b_value_decode(str)
260
- RubyVer.b_value_decode(str)
235
+ Utilities.b_value_decode(str)
261
236
  end
262
237
 
263
238
  # Decodes a Quoted-Printable string from the "=?UTF-8?Q?This_is_=E3=81=82_string?=" format
@@ -267,11 +242,7 @@ module Mail
267
242
  # Encodings.q_value_decode("=?UTF-8?Q?This_is_=E3=81=82_string?=")
268
243
  # #=> 'This is あ string'
269
244
  def Encodings.q_value_decode(str)
270
- RubyVer.q_value_decode(str)
271
- end
272
-
273
- def Encodings.find_encoding(str)
274
- RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
245
+ Utilities.q_value_decode(str)
275
246
  end
276
247
 
277
248
  # Gets the encoding type (Q or B) from the string.
@@ -329,7 +300,7 @@ module Mail
329
300
  charsize = chr.bytesize
330
301
 
331
302
  if chunksize + charsize > max_bytesize_per_chunk
332
- yield RubyVer.string_byteslice(str, offset, chunksize)
303
+ yield Utilities.string_byteslice(str, offset, chunksize)
333
304
  offset += chunksize
334
305
  chunksize = charsize
335
306
  else
@@ -337,7 +308,7 @@ module Mail
337
308
  end
338
309
  end
339
310
 
340
- yield RubyVer.string_byteslice(str, offset, chunksize)
311
+ yield Utilities.string_byteslice(str, offset, chunksize)
341
312
  end
342
313
  end
343
314
  end
data/lib/mail/envelope.rb CHANGED
@@ -1,31 +1,28 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
- #
3
+ #
4
4
  # = Mail Envelope
5
- #
5
+ #
6
6
  # The Envelope class provides a field for the first line in an
7
7
  # mbox file, that looks like "From mikel@test.lindsaar.net DATETIME"
8
- #
8
+ #
9
9
  # This envelope class reads that line, and turns it into an
10
10
  # Envelope.from and Envelope.date for your use.
11
+
11
12
  module Mail
12
- class Envelope < StructuredField
13
-
14
- def initialize(*args)
15
- super(FIELD_NAME, args.last.to_s)
16
- end
17
-
13
+ class Envelope < NamedStructuredField
14
+ NAME = 'Envelope-From'
15
+
18
16
  def element
19
17
  @element ||= Mail::EnvelopeFromElement.new(value)
20
18
  end
21
-
22
- def date
23
- ::DateTime.parse("#{element.date_time}")
24
- end
25
19
 
26
20
  def from
27
21
  element.address
28
22
  end
29
-
23
+
24
+ def date
25
+ element.date_time
26
+ end
30
27
  end
31
28
  end
data/lib/mail/field.rb CHANGED
@@ -23,8 +23,6 @@ module Mail
23
23
  # sections 3 and 4 of this standard.
24
24
  #
25
25
  class Field
26
-
27
- include Utilities
28
26
  include Comparable
29
27
 
30
28
  STRUCTURED_FIELDS = %w[ bcc cc content-description content-disposition
@@ -70,7 +68,7 @@ module Mail
70
68
  }
71
69
 
72
70
  FIELD_NAME_MAP = FIELDS_MAP.inject({}) do |map, (field, field_klass)|
73
- map.update(field => field_klass::CAPITALIZED_FIELD)
71
+ map.update(field => field_klass::NAME)
74
72
  end
75
73
 
76
74
  # Generic Field Exception
@@ -121,7 +119,7 @@ module Mail
121
119
  #
122
120
  # Mail::Field.parse("field-name: field data")
123
121
  # # => #<Mail::Field …>
124
- def parse(field, charset = nil)
122
+ def parse(field, charset = 'utf-8')
125
123
  name, value = split(field)
126
124
  if name && value
127
125
  new name, value, charset
@@ -145,6 +143,10 @@ module Mail
145
143
  warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: #{error.class}: #{error.message}"
146
144
  nil
147
145
  end
146
+
147
+ def field_class_for(name) #:nodoc:
148
+ FIELDS_MAP[name.to_s.downcase]
149
+ end
148
150
  end
149
151
 
150
152
  attr_reader :unparsed_value
@@ -163,10 +165,8 @@ module Mail
163
165
  # # => #<Mail::Field …>
164
166
  def initialize(name, value = nil, charset = 'utf-8')
165
167
  case
166
- when name.index(COLON)
167
- Kernel.warn 'Passing an unparsed header field to Mail::Field.new is deprecated and will be removed in Mail 2.8.0. Use Mail::Field.parse instead.'
168
- @name, @unparsed_value = self.class.split(name)
169
- @charset = Utilities.blank?(value) ? charset : value
168
+ when name.index(Constants::COLON)
169
+ raise ArgumentError, 'Passing an unparsed header field to Mail::Field.new is not supported in Mail 2.8.0+. Use Mail::Field.parse instead.'
170
170
  when Utilities.blank?(value)
171
171
  @name = name
172
172
  @unparsed_value = nil
@@ -179,8 +179,8 @@ module Mail
179
179
  @name = FIELD_NAME_MAP[@name.to_s.downcase] || @name
180
180
  end
181
181
 
182
- def field=(value)
183
- @field = value
182
+ def field=(field)
183
+ @field = field
184
184
  end
185
185
 
186
186
  def field
@@ -209,81 +209,65 @@ module Mail
209
209
  end.join(" ")}>"
210
210
  end
211
211
 
212
- def update(name, value)
213
- @field = create_field(name, value, @charset)
214
- end
215
-
216
- def same( other )
217
- return false unless other.kind_of?(self.class)
218
- match_to_s(other.name, self.name)
212
+ def same(other)
213
+ other.kind_of?(self.class) && Utilities.match_to_s(other.name, name)
219
214
  end
220
215
 
221
- def ==( other )
222
- return false unless other.kind_of?(self.class)
223
- match_to_s(other.name, self.name) && match_to_s(other.value, self.value)
216
+ def ==(other)
217
+ same(other) && Utilities.match_to_s(other.value, value)
224
218
  end
225
219
 
226
- def responsible_for?( val )
227
- name.to_s.casecmp(val.to_s) == 0
220
+ def responsible_for?(field_name)
221
+ name.to_s.casecmp(field_name.to_s) == 0
228
222
  end
229
223
 
230
- def <=>( other )
231
- self.field_order_id <=> other.field_order_id
224
+ def <=>(other)
225
+ field_order_id <=> other.field_order_id
232
226
  end
233
227
 
234
228
  def field_order_id
235
- @field_order_id ||= (FIELD_ORDER_LOOKUP[self.name.to_s.downcase] || 100)
229
+ @field_order_id ||= FIELD_ORDER_LOOKUP.fetch(self.name.to_s.downcase, 100)
236
230
  end
237
231
 
238
232
  def method_missing(name, *args, &block)
239
233
  field.send(name, *args, &block)
240
234
  end
241
235
 
242
- if RUBY_VERSION >= '1.9.2'
243
- def respond_to_missing?(method_name, include_private)
244
- field.respond_to?(method_name, include_private) || super
245
- end
246
- else
247
- def respond_to?(method_name, include_private = false)
248
- field.respond_to?(method_name, include_private) || super
249
- end
236
+ def respond_to_missing?(method_name, include_private)
237
+ field.respond_to?(method_name, include_private) || super
250
238
  end
251
239
 
252
- FIELD_ORDER = %w[ return-path received
253
- resent-date resent-from resent-sender resent-to
254
- resent-cc resent-bcc resent-message-id
255
- date from sender reply-to to cc bcc
256
- message-id in-reply-to references
257
- subject comments keywords
258
- mime-version content-type content-transfer-encoding
259
- content-location content-disposition content-description ]
260
-
261
- FIELD_ORDER_LOOKUP = Hash[FIELD_ORDER.each_with_index.to_a]
240
+ FIELD_ORDER_LOOKUP = Hash[%w[
241
+ return-path received
242
+ resent-date resent-from resent-sender resent-to
243
+ resent-cc resent-bcc resent-message-id
244
+ date from sender reply-to to cc bcc
245
+ message-id in-reply-to references
246
+ subject comments keywords
247
+ mime-version content-type content-transfer-encoding
248
+ content-location content-disposition content-description
249
+ ].each_with_index.to_a]
262
250
 
263
251
  private
264
252
 
265
253
  def create_field(name, value, charset)
266
- new_field(name, value, charset)
254
+ parse_field(name, value, charset)
267
255
  rescue Mail::Field::ParseError => e
268
256
  field = Mail::UnstructuredField.new(name, value)
269
257
  field.errors << [name, value, e]
270
258
  field
271
259
  end
272
260
 
273
- def new_field(name, value, charset)
261
+ def parse_field(name, value, charset)
274
262
  value = unfold(value) if value.is_a?(String)
275
263
 
276
- if klass = field_class_for(name)
277
- klass.new(value, charset)
264
+ if klass = self.class.field_class_for(name)
265
+ klass.parse(value, charset)
278
266
  else
279
- OptionalField.new(name, value, charset)
267
+ OptionalField.parse(name, value, charset)
280
268
  end
281
269
  end
282
270
 
283
- def field_class_for(name)
284
- FIELDS_MAP[name.to_s.downcase]
285
- end
286
-
287
271
  # 2.2.3. Long Header Fields
288
272
  #
289
273
  # The process of moving from this folded multiple-line representation
@@ -293,7 +277,7 @@ module Mail
293
277
  # treated in its unfolded form for further syntactic and semantic
294
278
  # evaluation.
295
279
  def unfold(string)
296
- string.gsub(/#{Constants::CRLF}(#{Constants::WSP})/m, '\1')
280
+ string.gsub(Constants::UNFOLD_WS, '\1')
297
281
  end
298
282
  end
299
283
  end
@@ -6,8 +6,36 @@ module Mail
6
6
  # email fields in order. And allows you to insert new fields without
7
7
  # having to worry about the order they will appear in.
8
8
  class FieldList < Array
9
+ def has_field?(field_name)
10
+ any? { |f| f.responsible_for? field_name }
11
+ end
12
+
13
+ def get_field(field_name)
14
+ fields = select_fields(field_name)
15
+ case fields.size
16
+ when 0; nil
17
+ when 1; fields.first
18
+ else fields
19
+ end
20
+ end
9
21
 
10
- include Enumerable
22
+ def add_field(field)
23
+ if field.singular?
24
+ replace_field field
25
+ else
26
+ insert_field field
27
+ end
28
+ end
29
+ alias_method :<<, :add_field
30
+
31
+ def replace_field(field)
32
+ if first_offset = index { |f| f.responsible_for? field.name }
33
+ delete_field field.name
34
+ insert first_offset, field
35
+ else
36
+ insert_field field
37
+ end
38
+ end
11
39
 
12
40
  # Insert the field in sorted order.
13
41
  #
@@ -15,20 +43,45 @@ module Mail
15
43
  # Copyright (C) 2001-2013 Python Software Foundation.
16
44
  # Licensed under <http://docs.python.org/license.html>
17
45
  # From <http://hg.python.org/cpython/file/2.7/Lib/bisect.py>
18
- def <<( new_field )
19
- lo = 0
20
- hi = size
21
-
46
+ def insert_field(field)
47
+ lo, hi = 0, size
22
48
  while lo < hi
23
49
  mid = (lo + hi).div(2)
24
- if new_field < self[mid]
50
+ if field < self[mid]
25
51
  hi = mid
26
52
  else
27
53
  lo = mid + 1
28
54
  end
29
55
  end
30
56
 
31
- insert(lo, new_field)
57
+ insert lo, field
58
+ end
59
+
60
+ def delete_field(name)
61
+ delete_if { |f| f.responsible_for? name }
62
+ end
63
+
64
+ def summary
65
+ map { |f| "<#{f.name}: #{f.value}>" }.join(", ")
66
+ end
67
+
68
+ private
69
+
70
+ def select_fields(field_name)
71
+ fields = select { |f| f.responsible_for? field_name }
72
+ if fields.size > 1 && singular?(field_name)
73
+ Array(fields.detect { |f| f.errors.size == 0 } || fields.first)
74
+ else
75
+ fields
76
+ end
77
+ end
78
+
79
+ def singular?(field_name)
80
+ if klass = Mail::Field.field_class_for(field_name)
81
+ klass.singular?
82
+ else
83
+ false
84
+ end
32
85
  end
33
86
  end
34
87
  end