mail 2.7.1 → 2.8.0.1

Sign up to get free protection for your applications and to get access to all the features.
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 +3 -3
  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