mail 2.6.3 → 2.7.1

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 (178) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +91 -79
  4. data/lib/mail/attachments_list.rb +11 -5
  5. data/lib/mail/body.rb +54 -41
  6. data/lib/mail/check_delivery_params.rb +50 -10
  7. data/lib/mail/configuration.rb +3 -0
  8. data/lib/mail/constants.rb +5 -3
  9. data/lib/mail/core_extensions/smtp.rb +20 -16
  10. data/lib/mail/core_extensions/string.rb +1 -30
  11. data/lib/mail/elements/address.rb +43 -32
  12. data/lib/mail/elements/address_list.rb +11 -18
  13. data/lib/mail/elements/content_disposition_element.rb +9 -15
  14. data/lib/mail/elements/content_location_element.rb +8 -12
  15. data/lib/mail/elements/content_transfer_encoding_element.rb +6 -10
  16. data/lib/mail/elements/content_type_element.rb +9 -19
  17. data/lib/mail/elements/date_time_element.rb +7 -14
  18. data/lib/mail/elements/envelope_from_element.rb +15 -21
  19. data/lib/mail/elements/message_ids_element.rb +12 -14
  20. data/lib/mail/elements/mime_version_element.rb +7 -14
  21. data/lib/mail/elements/phrase_list.rb +7 -9
  22. data/lib/mail/elements/received_element.rb +10 -15
  23. data/lib/mail/elements.rb +1 -0
  24. data/lib/mail/encodings/7bit.rb +6 -15
  25. data/lib/mail/encodings/8bit.rb +5 -18
  26. data/lib/mail/encodings/base64.rb +15 -10
  27. data/lib/mail/encodings/binary.rb +4 -22
  28. data/lib/mail/encodings/identity.rb +24 -0
  29. data/lib/mail/encodings/quoted_printable.rb +13 -7
  30. data/lib/mail/encodings/transfer_encoding.rb +47 -28
  31. data/lib/mail/encodings/unix_to_unix.rb +4 -1
  32. data/lib/mail/encodings.rb +114 -60
  33. data/lib/mail/envelope.rb +2 -1
  34. data/lib/mail/field.rb +114 -62
  35. data/lib/mail/field_list.rb +1 -0
  36. data/lib/mail/fields/bcc_field.rb +17 -5
  37. data/lib/mail/fields/cc_field.rb +2 -2
  38. data/lib/mail/fields/comments_field.rb +2 -1
  39. data/lib/mail/fields/common/address_container.rb +3 -2
  40. data/lib/mail/fields/common/common_address.rb +40 -14
  41. data/lib/mail/fields/common/common_date.rb +2 -1
  42. data/lib/mail/fields/common/common_field.rb +5 -11
  43. data/lib/mail/fields/common/common_message_id.rb +3 -2
  44. data/lib/mail/fields/common/parameter_hash.rb +2 -1
  45. data/lib/mail/fields/content_description_field.rb +2 -1
  46. data/lib/mail/fields/content_disposition_field.rb +14 -13
  47. data/lib/mail/fields/content_id_field.rb +5 -4
  48. data/lib/mail/fields/content_location_field.rb +3 -2
  49. data/lib/mail/fields/content_transfer_encoding_field.rb +3 -2
  50. data/lib/mail/fields/content_type_field.rb +7 -11
  51. data/lib/mail/fields/date_field.rb +4 -4
  52. data/lib/mail/fields/from_field.rb +2 -2
  53. data/lib/mail/fields/in_reply_to_field.rb +2 -1
  54. data/lib/mail/fields/keywords_field.rb +3 -3
  55. data/lib/mail/fields/message_id_field.rb +3 -2
  56. data/lib/mail/fields/mime_version_field.rb +4 -3
  57. data/lib/mail/fields/optional_field.rb +5 -1
  58. data/lib/mail/fields/received_field.rb +5 -4
  59. data/lib/mail/fields/references_field.rb +2 -1
  60. data/lib/mail/fields/reply_to_field.rb +2 -2
  61. data/lib/mail/fields/resent_bcc_field.rb +2 -2
  62. data/lib/mail/fields/resent_cc_field.rb +2 -2
  63. data/lib/mail/fields/resent_date_field.rb +2 -2
  64. data/lib/mail/fields/resent_from_field.rb +2 -2
  65. data/lib/mail/fields/resent_message_id_field.rb +2 -1
  66. data/lib/mail/fields/resent_sender_field.rb +2 -2
  67. data/lib/mail/fields/resent_to_field.rb +2 -2
  68. data/lib/mail/fields/return_path_field.rb +2 -2
  69. data/lib/mail/fields/sender_field.rb +2 -2
  70. data/lib/mail/fields/structured_field.rb +1 -0
  71. data/lib/mail/fields/subject_field.rb +2 -1
  72. data/lib/mail/fields/to_field.rb +2 -2
  73. data/lib/mail/fields/unstructured_field.rb +25 -7
  74. data/lib/mail/fields.rb +1 -0
  75. data/lib/mail/header.rb +15 -12
  76. data/lib/mail/indifferent_hash.rb +1 -0
  77. data/lib/mail/mail.rb +3 -10
  78. data/lib/mail/matchers/attachment_matchers.rb +29 -0
  79. data/lib/mail/matchers/has_sent_mail.rb +51 -7
  80. data/lib/mail/message.rb +91 -86
  81. data/lib/mail/multibyte/chars.rb +32 -30
  82. data/lib/mail/multibyte/unicode.rb +31 -26
  83. data/lib/mail/multibyte/utils.rb +1 -0
  84. data/lib/mail/multibyte.rb +65 -15
  85. data/lib/mail/network/delivery_methods/exim.rb +7 -10
  86. data/lib/mail/network/delivery_methods/file_delivery.rb +5 -8
  87. data/lib/mail/network/delivery_methods/logger_delivery.rb +37 -0
  88. data/lib/mail/network/delivery_methods/sendmail.rb +17 -11
  89. data/lib/mail/network/delivery_methods/smtp.rb +60 -53
  90. data/lib/mail/network/delivery_methods/smtp_connection.rb +11 -6
  91. data/lib/mail/network/delivery_methods/test_mailer.rb +6 -8
  92. data/lib/mail/network/retriever_methods/base.rb +1 -0
  93. data/lib/mail/network/retriever_methods/imap.rb +19 -5
  94. data/lib/mail/network/retriever_methods/pop3.rb +4 -1
  95. data/lib/mail/network/retriever_methods/test_retriever.rb +2 -1
  96. data/lib/mail/network.rb +2 -0
  97. data/lib/mail/parser_tools.rb +15 -0
  98. data/lib/mail/parsers/address_lists_parser.rb +33208 -104
  99. data/lib/mail/parsers/address_lists_parser.rl +172 -0
  100. data/lib/mail/parsers/content_disposition_parser.rb +877 -49
  101. data/lib/mail/parsers/content_disposition_parser.rl +82 -0
  102. data/lib/mail/parsers/content_location_parser.rb +804 -23
  103. data/lib/mail/parsers/content_location_parser.rl +71 -0
  104. data/lib/mail/parsers/content_transfer_encoding_parser.rb +502 -19
  105. data/lib/mail/parsers/content_transfer_encoding_parser.rl +64 -0
  106. data/lib/mail/parsers/content_type_parser.rb +1024 -48
  107. data/lib/mail/parsers/content_type_parser.rl +83 -0
  108. data/lib/mail/parsers/date_time_parser.rb +872 -23
  109. data/lib/mail/parsers/date_time_parser.rl +62 -0
  110. data/lib/mail/parsers/envelope_from_parser.rb +3570 -34
  111. data/lib/mail/parsers/envelope_from_parser.rl +82 -0
  112. data/lib/mail/parsers/message_ids_parser.rb +2840 -25
  113. data/lib/mail/parsers/message_ids_parser.rl +82 -0
  114. data/lib/mail/parsers/mime_version_parser.rb +492 -26
  115. data/lib/mail/parsers/mime_version_parser.rl +61 -0
  116. data/lib/mail/parsers/phrase_lists_parser.rb +862 -17
  117. data/lib/mail/parsers/phrase_lists_parser.rl +83 -0
  118. data/lib/mail/parsers/received_parser.rb +8765 -36
  119. data/lib/mail/parsers/received_parser.rl +84 -0
  120. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  121. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  122. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  123. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  124. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  125. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  126. data/lib/mail/parsers/rfc5322.rl +59 -0
  127. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  128. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  129. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  130. data/lib/mail/parsers.rb +17 -24
  131. data/lib/mail/part.rb +8 -5
  132. data/lib/mail/parts_list.rb +31 -14
  133. data/lib/mail/utilities.rb +109 -10
  134. data/lib/mail/values/unicode_tables.dat +0 -0
  135. data/lib/mail/version.rb +3 -2
  136. data/lib/mail/version_specific/ruby_1_8.rb +50 -6
  137. data/lib/mail/version_specific/ruby_1_9.rb +103 -18
  138. data/lib/mail.rb +5 -12
  139. metadata +47 -57
  140. data/CHANGELOG.rdoc +0 -759
  141. data/CONTRIBUTING.md +0 -60
  142. data/Dependencies.txt +0 -2
  143. data/Gemfile +0 -15
  144. data/Rakefile +0 -29
  145. data/TODO.rdoc +0 -9
  146. data/lib/mail/core_extensions/nil.rb +0 -19
  147. data/lib/mail/core_extensions/object.rb +0 -13
  148. data/lib/mail/core_extensions/string/access.rb +0 -145
  149. data/lib/mail/core_extensions/string/multibyte.rb +0 -78
  150. data/lib/mail/multibyte/exceptions.rb +0 -8
  151. data/lib/mail/parsers/ragel/common.rl +0 -185
  152. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  153. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  154. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  155. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  156. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  157. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  158. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  159. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  160. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  161. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  162. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  163. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  164. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  165. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2149
  166. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  167. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  168. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  169. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  170. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  171. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  172. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  173. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  174. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  175. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  176. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  177. data/lib/mail/parsers/ragel/ruby.rb +0 -40
  178. data/lib/mail/parsers/ragel.rb +0 -17
data/lib/mail/message.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require "yaml"
3
4
 
4
5
  module Mail
@@ -55,15 +56,23 @@ module Mail
55
56
  #
56
57
  # ===Making an email via a block
57
58
  #
58
- # mail = Mail.new do
59
- # from 'mikel@test.lindsaar.net'
60
- # to 'you@test.lindsaar.net'
61
- # subject 'This is a test email'
62
- # body File.read('body.txt')
59
+ # mail = Mail.new do |m|
60
+ # m.from 'mikel@test.lindsaar.net'
61
+ # m.to 'you@test.lindsaar.net'
62
+ # m.subject 'This is a test email'
63
+ # m.body File.read('body.txt')
63
64
  # end
64
65
  #
65
66
  # mail.to_s #=> "From: mikel@test.lindsaar.net\r\nTo: you@...
66
67
  #
68
+ # If may also pass a block with no arguments, in which case it will
69
+ # be evaluated in the scope of the new message instance:
70
+ #
71
+ # mail = Mail.new do
72
+ # from 'mikel@test.lindsaar.net'
73
+ # # …
74
+ # end
75
+ #
67
76
  # ===Making an email via passing a string
68
77
  #
69
78
  # mail = Mail.new("To: mikel@test.lindsaar.net\r\nSubject: Hello\r\n\r\nHi there!")
@@ -128,8 +137,23 @@ module Mail
128
137
  init_with_string(args.flatten[0].to_s)
129
138
  end
130
139
 
140
+ # Support both builder styles:
141
+ #
142
+ # Mail.new do
143
+ # to 'recipient@example.com'
144
+ # end
145
+ #
146
+ # and
147
+ #
148
+ # Mail.new do |m|
149
+ # m.to 'recipient@example.com'
150
+ # end
131
151
  if block_given?
132
- instance_eval(&block)
152
+ if block.arity.zero? || (RUBY_VERSION < '1.9' && block.arity < 1)
153
+ instance_eval(&block)
154
+ else
155
+ yield self
156
+ end
133
157
  end
134
158
 
135
159
  self
@@ -212,7 +236,7 @@ module Mail
212
236
  self.default_charset = 'UTF-8'
213
237
 
214
238
  def register_for_delivery_notification(observer)
215
- STDERR.puts("Message#register_for_delivery_notification is deprecated, please call Mail.register_observer instead")
239
+ warn("Message#register_for_delivery_notification is deprecated, please call Mail.register_observer instead")
216
240
  Mail.register_observer(observer)
217
241
  end
218
242
 
@@ -224,7 +248,7 @@ module Mail
224
248
  Mail.inform_interceptors(self)
225
249
  end
226
250
 
227
- # Delivers an mail object.
251
+ # Delivers a mail object.
228
252
  #
229
253
  # Examples:
230
254
  #
@@ -359,13 +383,8 @@ module Mail
359
383
  if self.message_id && other.message_id
360
384
  self.encoded == other.encoded
361
385
  else
362
- self_message_id, other_message_id = self.message_id, other.message_id
363
- begin
364
- self.message_id, other.message_id = '<temp@test>', '<temp@test>'
365
- self.encoded == other.encoded
366
- ensure
367
- self.message_id, other.message_id = self_message_id, other_message_id
368
- end
386
+ dup.tap { |m| m.message_id = '<temp@test>' }.encoded ==
387
+ other.dup.tap { |m| m.message_id = '<temp@test>' }.encoded
369
388
  end
370
389
  end
371
390
 
@@ -1192,8 +1211,8 @@ module Mail
1192
1211
  def default( sym, val = nil )
1193
1212
  if val
1194
1213
  header[sym] = val
1195
- else
1196
- header[sym].default if header[sym]
1214
+ elsif field = header[sym]
1215
+ field.default
1197
1216
  end
1198
1217
  end
1199
1218
 
@@ -1239,14 +1258,13 @@ module Mail
1239
1258
  def body(value = nil)
1240
1259
  if value
1241
1260
  self.body = value
1242
- # add_encoding_to_body
1243
1261
  else
1244
1262
  process_body_raw if @body_raw
1245
1263
  @body
1246
1264
  end
1247
1265
  end
1248
1266
 
1249
- def body_encoding(value)
1267
+ def body_encoding(value = nil)
1250
1268
  if value.nil?
1251
1269
  body.encoding
1252
1270
  else
@@ -1255,7 +1273,7 @@ module Mail
1255
1273
  end
1256
1274
 
1257
1275
  def body_encoding=(value)
1258
- body.encoding = value
1276
+ body.encoding = value
1259
1277
  end
1260
1278
 
1261
1279
  # Returns the list of addresses this message should be sent to by
@@ -1415,11 +1433,11 @@ module Mail
1415
1433
  end
1416
1434
 
1417
1435
  def has_content_transfer_encoding?
1418
- header[:content_transfer_encoding] && header[:content_transfer_encoding].errors.blank?
1436
+ header[:content_transfer_encoding] && Utilities.blank?(header[:content_transfer_encoding].errors)
1419
1437
  end
1420
1438
 
1421
1439
  def has_transfer_encoding? # :nodoc:
1422
- STDERR.puts(":has_transfer_encoding? is deprecated in Mail 1.4.3. Please use has_content_transfer_encoding?\n#{caller}")
1440
+ warn(":has_transfer_encoding? is deprecated in Mail 1.4.3. Please use has_content_transfer_encoding?\n#{caller}")
1423
1441
  has_content_transfer_encoding?
1424
1442
  end
1425
1443
 
@@ -1467,34 +1485,26 @@ module Mail
1467
1485
  if !body.empty?
1468
1486
  # Only give a warning if this isn't an attachment, has non US-ASCII and the user
1469
1487
  # has not specified an encoding explicitly.
1470
- if @defaulted_charset && body.raw_source.not_ascii_only? && !self.attachment?
1488
+ if @defaulted_charset && !body.raw_source.ascii_only? && !self.attachment?
1471
1489
  warning = "Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.\n"
1472
- STDERR.puts(warning)
1490
+ warn(warning)
1473
1491
  end
1474
1492
  header[:content_type].parameters['charset'] = @charset
1475
1493
  end
1476
1494
  end
1477
1495
 
1478
1496
  # Adds a content transfer encoding
1479
- #
1480
- # Otherwise raises a warning
1481
1497
  def add_content_transfer_encoding
1482
- if body.only_us_ascii?
1483
- header[:content_transfer_encoding] = '7bit'
1484
- else
1485
- warning = "Non US-ASCII detected and no content-transfer-encoding defined.\nDefaulting to 8bit, set your own if this is incorrect.\n"
1486
- STDERR.puts(warning)
1487
- header[:content_transfer_encoding] = '8bit'
1488
- end
1498
+ header[:content_transfer_encoding] ||= body.default_encoding
1489
1499
  end
1490
1500
 
1491
1501
  def add_transfer_encoding # :nodoc:
1492
- STDERR.puts(":add_transfer_encoding is deprecated in Mail 1.4.3. Please use add_content_transfer_encoding\n#{caller}")
1502
+ warn(":add_transfer_encoding is deprecated in Mail 1.4.3. Please use add_content_transfer_encoding\n#{caller}")
1493
1503
  add_content_transfer_encoding
1494
1504
  end
1495
1505
 
1496
1506
  def transfer_encoding # :nodoc:
1497
- STDERR.puts(":transfer_encoding is deprecated in Mail 1.4.3. Please use content_transfer_encoding\n#{caller}")
1507
+ warn(":transfer_encoding is deprecated in Mail 1.4.3. Please use content_transfer_encoding\n#{caller}")
1498
1508
  content_transfer_encoding
1499
1509
  end
1500
1510
 
@@ -1504,7 +1514,7 @@ module Mail
1504
1514
  end
1505
1515
 
1506
1516
  def message_content_type
1507
- STDERR.puts(":message_content_type is deprecated in Mail 1.4.3. Please use mime_type\n#{caller}")
1517
+ warn(":message_content_type is deprecated in Mail 1.4.3. Please use mime_type\n#{caller}")
1508
1518
  mime_type
1509
1519
  end
1510
1520
 
@@ -1536,7 +1546,7 @@ module Mail
1536
1546
 
1537
1547
  # Returns the content type parameters
1538
1548
  def mime_parameters
1539
- STDERR.puts(':mime_parameters is deprecated in Mail 1.4.3, please use :content_type_parameters instead')
1549
+ warn(':mime_parameters is deprecated in Mail 1.4.3, please use :content_type_parameters instead')
1540
1550
  content_type_parameters
1541
1551
  end
1542
1552
 
@@ -1562,7 +1572,14 @@ module Mail
1562
1572
 
1563
1573
  # returns the part in a multipart/report email that has the content-type delivery-status
1564
1574
  def delivery_status_part
1565
- @delivery_stats_part ||= parts.select { |p| p.delivery_status_report_part? }.first
1575
+ unless defined? @delivery_status_part
1576
+ @delivery_status_part =
1577
+ if delivery_status_report?
1578
+ parts.detect(&:delivery_status_report_part?)
1579
+ end
1580
+ end
1581
+
1582
+ @delivery_status_part
1566
1583
  end
1567
1584
 
1568
1585
  def bounced?
@@ -1669,6 +1686,8 @@ module Mail
1669
1686
  def html_part=(msg)
1670
1687
  # Assign the html part and set multipart/alternative if there's a text part.
1671
1688
  if msg
1689
+ msg = Mail::Part.new(:body => msg) unless msg.kind_of?(Mail::Message)
1690
+
1672
1691
  @html_part = msg
1673
1692
  @html_part.content_type = 'text/html' unless @html_part.has_content_type?
1674
1693
  add_multipart_alternate_header if text_part
@@ -1691,6 +1710,8 @@ module Mail
1691
1710
  def text_part=(msg)
1692
1711
  # Assign the text part and set multipart/alternative if there's an html part.
1693
1712
  if msg
1713
+ msg = Mail::Part.new(:body => msg) unless msg.kind_of?(Mail::Message)
1714
+
1694
1715
  @text_part = msg
1695
1716
  @text_part.content_type = 'text/plain' unless @text_part.has_content_type?
1696
1717
  add_multipart_alternate_header if html_part
@@ -1709,7 +1730,7 @@ module Mail
1709
1730
 
1710
1731
  # Adds a part to the parts list or creates the part list
1711
1732
  def add_part(part)
1712
- if !body.multipart? && !self.body.decoded.blank?
1733
+ if !body.multipart? && !Utilities.blank?(self.body.decoded)
1713
1734
  @text_part = Mail::Part.new('Content-Type: text/plain;')
1714
1735
  @text_part.body = body.decoded
1715
1736
  self.body << @text_part
@@ -1765,14 +1786,14 @@ module Mail
1765
1786
  #
1766
1787
  # See also #attachments
1767
1788
  def add_file(values)
1768
- convert_to_multipart unless self.multipart? || self.body.decoded.blank?
1789
+ convert_to_multipart unless self.multipart? || Utilities.blank?(self.body.decoded)
1769
1790
  add_multipart_mixed_header
1770
1791
  if values.is_a?(String)
1771
1792
  basename = File.basename(values)
1772
1793
  filedata = File.open(values, 'rb') { |f| f.read }
1773
1794
  else
1774
1795
  basename = values[:filename]
1775
- filedata = values[:content] || File.open(values[:filename], 'rb') { |f| f.read }
1796
+ filedata = values
1776
1797
  end
1777
1798
  self.attachments[basename] = filedata
1778
1799
  end
@@ -1790,7 +1811,6 @@ module Mail
1790
1811
  # ready to send
1791
1812
  def ready_to_send!
1792
1813
  identify_and_set_transfer_encoding
1793
- parts.sort!([ "text/plain", "text/enriched", "text/html", "multipart/alternative" ])
1794
1814
  parts.each do |part|
1795
1815
  part.transport_encoding = transport_encoding
1796
1816
  part.ready_to_send!
@@ -1799,7 +1819,7 @@ module Mail
1799
1819
  end
1800
1820
 
1801
1821
  def encode!
1802
- STDERR.puts("Deprecated in 1.1.0 in favour of :ready_to_send! as it is less confusing with encoding and decoding.")
1822
+ warn("Deprecated in 1.1.0 in favour of :ready_to_send! as it is less confusing with encoding and decoding.")
1803
1823
  ready_to_send!
1804
1824
  end
1805
1825
 
@@ -1815,16 +1835,13 @@ module Mail
1815
1835
  end
1816
1836
 
1817
1837
  def without_attachments!
1818
- return self unless has_attachments?
1819
-
1820
- parts.delete_if { |p| p.attachment? }
1821
- body_raw = if parts.empty?
1822
- ''
1823
- else
1824
- body.encoded
1825
- end
1838
+ if has_attachments?
1839
+ parts.delete_if { |p| p.attachment? }
1826
1840
 
1827
- @body = Mail::Body.new(body_raw)
1841
+ reencoded = parts.empty? ? '' : body.encoded(content_transfer_encoding)
1842
+ @body = nil # So the new parts won't be added to the existing body
1843
+ self.body = reencoded
1844
+ end
1828
1845
 
1829
1846
  self
1830
1847
  end
@@ -1857,7 +1874,7 @@ module Mail
1857
1874
  case
1858
1875
  when k == 'delivery_handler'
1859
1876
  begin
1860
- m.delivery_handler = Object.const_get(v) unless v.blank?
1877
+ m.delivery_handler = Object.const_get(v) unless Utilities.blank?(v)
1861
1878
  rescue NameError
1862
1879
  end
1863
1880
  when k == 'transport_encoding'
@@ -1967,7 +1984,7 @@ module Mail
1967
1984
 
1968
1985
  private
1969
1986
 
1970
- HEADER_SEPARATOR = /#{CRLF}#{CRLF}|#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m
1987
+ HEADER_SEPARATOR = /#{Constants::CRLF}#{Constants::CRLF}/
1971
1988
 
1972
1989
  # 2.1. General Description
1973
1990
  # A message consists of header fields (collectively called "the header
@@ -1976,9 +1993,6 @@ module Mail
1976
1993
  # this standard. The body is simply a sequence of characters that
1977
1994
  # follows the header and is separated from the header by an empty line
1978
1995
  # (i.e., a line with nothing preceding the CRLF).
1979
- #
1980
- # Additionally, I allow for the case where someone might have put whitespace
1981
- # on the "gap line"
1982
1996
  def parse_message
1983
1997
  header_part, body_part = raw_source.lstrip.split(HEADER_SEPARATOR, 2)
1984
1998
  self.header = header_part
@@ -1986,8 +2000,7 @@ module Mail
1986
2000
  end
1987
2001
 
1988
2002
  def raw_source=(value)
1989
- value = value.dup.force_encoding(Encoding::BINARY) if RUBY_VERSION >= "1.9.1"
1990
- @raw_source = value.to_crlf
2003
+ @raw_source = value
1991
2004
  end
1992
2005
 
1993
2006
  # see comments to body=. We take data and process it lazily
@@ -1999,11 +2012,9 @@ module Mail
1999
2012
  @body_raw = nil
2000
2013
  add_encoding_to_body
2001
2014
  when @body && @body.multipart?
2002
- @body << Mail::Part.new(value)
2003
- add_encoding_to_body
2015
+ self.text_part = value
2004
2016
  else
2005
2017
  @body_raw = value
2006
- # process_body_raw
2007
2018
  end
2008
2019
  end
2009
2020
 
@@ -2018,9 +2029,9 @@ module Mail
2018
2029
 
2019
2030
  def set_envelope_header
2020
2031
  raw_string = raw_source.to_s
2021
- if match_data = raw_source.to_s.match(/\AFrom\s(#{TEXT}+)#{CRLF}/m)
2032
+ if match_data = raw_string.match(/\AFrom\s(#{TEXT}+)#{Constants::CRLF}/m)
2022
2033
  set_envelope(match_data[1])
2023
- self.raw_source = raw_string.sub(match_data[0], "")
2034
+ self.raw_source = raw_string.sub(match_data[0], "")
2024
2035
  end
2025
2036
  end
2026
2037
 
@@ -2028,6 +2039,13 @@ module Mail
2028
2039
  body.split!(boundary)
2029
2040
  end
2030
2041
 
2042
+ def allowed_encodings
2043
+ case mime_type
2044
+ when 'message/rfc822'
2045
+ [Encodings::SevenBit, Encodings::EightBit, Encodings::Binary]
2046
+ end
2047
+ end
2048
+
2031
2049
  def add_encoding_to_body
2032
2050
  if has_content_transfer_encoding?
2033
2051
  @body.encoding = content_transfer_encoding
@@ -2035,18 +2053,18 @@ module Mail
2035
2053
  end
2036
2054
 
2037
2055
  def identify_and_set_transfer_encoding
2038
- if body && body.multipart?
2039
- self.content_transfer_encoding = @transport_encoding
2040
- else
2041
- self.content_transfer_encoding = body.get_best_encoding(@transport_encoding)
2042
- end
2056
+ if body && body.multipart?
2057
+ self.content_transfer_encoding = @transport_encoding
2058
+ else
2059
+ self.content_transfer_encoding = body.negotiate_best_encoding(@transport_encoding, allowed_encodings).to_s
2060
+ end
2043
2061
  end
2044
2062
 
2045
2063
  def add_required_fields
2046
2064
  add_required_message_fields
2047
2065
  add_multipart_mixed_header if body.multipart?
2048
2066
  add_content_type unless has_content_type?
2049
- add_charset unless has_charset?
2067
+ add_charset if text? && !has_charset?
2050
2068
  add_content_transfer_encoding unless has_content_transfer_encoding?
2051
2069
  end
2052
2070
 
@@ -2122,10 +2140,10 @@ module Mail
2122
2140
  content_disp_name = header[:content_disposition].filename rescue nil
2123
2141
  content_loc_name = header[:content_location].location rescue nil
2124
2142
  case
2125
- when content_type && content_type_name
2126
- filename = content_type_name
2127
2143
  when content_disposition && content_disp_name
2128
2144
  filename = content_disp_name
2145
+ when content_type && content_type_name
2146
+ filename = content_type_name
2129
2147
  when content_location && content_loc_name
2130
2148
  filename = content_loc_name
2131
2149
  else
@@ -2146,20 +2164,7 @@ module Mail
2146
2164
  end
2147
2165
 
2148
2166
  def decode_body_as_text
2149
- body_text = decode_body
2150
- if charset
2151
- if RUBY_VERSION < '1.9'
2152
- require 'iconv'
2153
- return Iconv.conv("UTF-8//TRANSLIT//IGNORE", charset, body_text)
2154
- else
2155
- if encoding = Encoding.find(charset) rescue nil
2156
- body_text.force_encoding(encoding)
2157
- return body_text.encode(Encoding::UTF_8, :undef => :replace, :invalid => :replace, :replace => '')
2158
- end
2159
- end
2160
- end
2161
- body_text
2167
+ Encodings.transcode_charset decode_body, charset, 'UTF-8'
2162
2168
  end
2163
-
2164
2169
  end
2165
2170
  end
@@ -1,4 +1,6 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/multibyte/unicode'
2
4
 
3
5
  module Mail #:nodoc:
4
6
  module Multibyte #:nodoc:
@@ -39,7 +41,7 @@ module Mail #:nodoc:
39
41
  if RUBY_VERSION >= "1.9"
40
42
  # Creates a new Chars instance by wrapping _string_.
41
43
  def initialize(string)
42
- @wrapped_string = string
44
+ @wrapped_string = string.dup
43
45
  @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
44
46
  end
45
47
  else
@@ -102,7 +104,7 @@ module Mail #:nodoc:
102
104
  # Returns a new Chars object containing the _other_ object concatenated to the string.
103
105
  #
104
106
  # Example:
105
- # ('Café'.mb_chars + ' périferôl').to_s # => "Café périferôl"
107
+ # (Mail::Multibyte.mb_chars('Café') + ' périferôl').to_s # => "Café périferôl"
106
108
  def +(other)
107
109
  chars(@wrapped_string + other)
108
110
  end
@@ -110,7 +112,7 @@ module Mail #:nodoc:
110
112
  # Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
111
113
  #
112
114
  # Example:
113
- # 'Café périferôl'.mb_chars =~ /ô/ # => 12
115
+ # Mail::Multibyte.mb_chars('Café périferôl') =~ /ô/ # => 12
114
116
  def =~(other)
115
117
  translate_offset(@wrapped_string =~ other)
116
118
  end
@@ -118,7 +120,7 @@ module Mail #:nodoc:
118
120
  # Inserts the passed string at specified codepoint offsets.
119
121
  #
120
122
  # Example:
121
- # 'Café'.mb_chars.insert(4, ' périferôl').to_s # => "Café périferôl"
123
+ # Mail::Multibyte.mb_chars('Café').insert(4, ' périferôl').to_s # => "Café périferôl"
122
124
  def insert(offset, fragment)
123
125
  unpacked = Unicode.u_unpack(@wrapped_string)
124
126
  unless offset > unpacked.length
@@ -134,7 +136,7 @@ module Mail #:nodoc:
134
136
  # Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
135
137
  #
136
138
  # Example:
137
- # 'Café'.mb_chars.include?('é') # => true
139
+ # Mail::Multibyte.mb_chars('Café').include?('é') # => true
138
140
  def include?(other)
139
141
  # We have to redefine this method because Enumerable defines it.
140
142
  @wrapped_string.include?(other)
@@ -143,8 +145,8 @@ module Mail #:nodoc:
143
145
  # Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
144
146
  #
145
147
  # Example:
146
- # 'Café périferôl'.mb_chars.index('ô') # => 12
147
- # 'Café périferôl'.mb_chars.index(/\w/u) # => 0
148
+ # Mail::Multibyte.mb_chars('Café périferôl').index('ô') # => 12
149
+ # Mail::Multibyte.mb_chars('Café périferôl').index(/\w/u) # => 0
148
150
  def index(needle, offset=0)
149
151
  wrapped_offset = first(offset).wrapped_string.length
150
152
  index = @wrapped_string.index(needle, wrapped_offset)
@@ -156,8 +158,8 @@ module Mail #:nodoc:
156
158
  # string. Returns +nil+ if _needle_ isn't found.
157
159
  #
158
160
  # Example:
159
- # 'Café périferôl'.mb_chars.rindex('é') # => 6
160
- # 'Café périferôl'.mb_chars.rindex(/\w/u) # => 13
161
+ # Mail::Multibyte.mb_chars('Café périferôl').rindex('é') # => 6
162
+ # Mail::Multibyte.mb_chars('Café périferôl').rindex(/\w/u) # => 13
161
163
  def rindex(needle, offset=nil)
162
164
  offset ||= length
163
165
  wrapped_offset = first(offset).wrapped_string.length
@@ -189,7 +191,7 @@ module Mail #:nodoc:
189
191
  # Returns the codepoint of the first character in the string.
190
192
  #
191
193
  # Example:
192
- # 'こんにちは'.mb_chars.ord # => 12371
194
+ # Mail::Multibyte.mb_chars('こんにちは').ord # => 12371
193
195
  def ord
194
196
  Unicode.u_unpack(@wrapped_string)[0]
195
197
  end
@@ -198,10 +200,10 @@ module Mail #:nodoc:
198
200
  #
199
201
  # Example:
200
202
  #
201
- # "¾ cup".mb_chars.rjust(8).to_s
203
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8).to_s
202
204
  # # => " ¾ cup"
203
205
  #
204
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
206
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8, " ").to_s # Use non-breaking whitespace
205
207
  # # => "   ¾ cup"
206
208
  def rjust(integer, padstr=' ')
207
209
  justify(integer, :right, padstr)
@@ -211,10 +213,10 @@ module Mail #:nodoc:
211
213
  #
212
214
  # Example:
213
215
  #
214
- # "¾ cup".mb_chars.rjust(8).to_s
216
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8).to_s
215
217
  # # => "¾ cup "
216
218
  #
217
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
219
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8, " ").to_s # Use non-breaking whitespace
218
220
  # # => "¾ cup   "
219
221
  def ljust(integer, padstr=' ')
220
222
  justify(integer, :left, padstr)
@@ -224,10 +226,10 @@ module Mail #:nodoc:
224
226
  #
225
227
  # Example:
226
228
  #
227
- # "¾ cup".mb_chars.center(8).to_s
229
+ # Mail::Multibyte.mb_chars("¾ cup").center(8).to_s
228
230
  # # => " ¾ cup "
229
231
  #
230
- # "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace
232
+ # Mail::Multibyte.mb_chars("¾ cup").center(8, " ").to_s # Use non-breaking whitespace
231
233
  # # => " ¾ cup  "
232
234
  def center(integer, padstr=' ')
233
235
  justify(integer, :center, padstr)
@@ -243,7 +245,7 @@ module Mail #:nodoc:
243
245
  # instances instead of String. This makes chaining methods easier.
244
246
  #
245
247
  # Example:
246
- # 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
248
+ # Mail::Multibyte.mb_chars('Café périferôl').split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
247
249
  def split(*args)
248
250
  @wrapped_string.split(*args).map { |i| i.mb_chars }
249
251
  end
@@ -268,12 +270,12 @@ module Mail #:nodoc:
268
270
  @wrapped_string[*args] = replace_by
269
271
  else
270
272
  result = Unicode.u_unpack(@wrapped_string)
271
- if args[0].is_a?(Fixnum)
273
+ if args[0].is_a?(Integer)
272
274
  raise IndexError, "index #{args[0]} out of string" if args[0] >= result.length
273
275
  min = args[0]
274
276
  max = args[1].nil? ? min : (min + args[1] - 1)
275
277
  range = Range.new(min, max)
276
- replace_by = [replace_by].pack('U') if replace_by.is_a?(Fixnum)
278
+ replace_by = [replace_by].pack('U') if replace_by.is_a?(Integer)
277
279
  elsif args.first.is_a?(Range)
278
280
  raise RangeError, "#{args[0]} out of range" if args[0].min >= result.length
279
281
  range = args[0]
@@ -291,7 +293,7 @@ module Mail #:nodoc:
291
293
  # Reverses all characters in the string.
292
294
  #
293
295
  # Example:
294
- # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
296
+ # Mail::Multibyte.mb_chars('Café').reverse.to_s # => 'éfaC'
295
297
  def reverse
296
298
  chars(Unicode.g_unpack(@wrapped_string).reverse.flatten.pack('U*'))
297
299
  end
@@ -300,7 +302,7 @@ module Mail #:nodoc:
300
302
  # character.
301
303
  #
302
304
  # Example:
303
- # 'こんにちは'.mb_chars.slice(2..3).to_s # => "にち"
305
+ # Mail::Multibyte.mb_chars('こんにちは').slice(2..3).to_s # => "にち"
304
306
  def slice(*args)
305
307
  if args.size > 2
306
308
  raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
@@ -337,7 +339,7 @@ module Mail #:nodoc:
337
339
  # Convert characters in the string to uppercase.
338
340
  #
339
341
  # Example:
340
- # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
342
+ # Mail::Multibyte.mb_chars('Laurent, où sont les tests ?').upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
341
343
  def upcase
342
344
  chars(Unicode.apply_mapping(@wrapped_string, :uppercase_mapping))
343
345
  end
@@ -345,7 +347,7 @@ module Mail #:nodoc:
345
347
  # Convert characters in the string to lowercase.
346
348
  #
347
349
  # Example:
348
- # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
350
+ # Mail::Multibyte.mb_chars('VĚDA A VÝZKUM').downcase.to_s # => "věda a výzkum"
349
351
  def downcase
350
352
  chars(Unicode.apply_mapping(@wrapped_string, :lowercase_mapping))
351
353
  end
@@ -353,7 +355,7 @@ module Mail #:nodoc:
353
355
  # Converts the first character to uppercase and the remainder to lowercase.
354
356
  #
355
357
  # Example:
356
- # 'über'.mb_chars.capitalize.to_s # => "Über"
358
+ # Mail::Multibyte.mb_chars('über').capitalize.to_s # => "Über"
357
359
  def capitalize
358
360
  (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
359
361
  end
@@ -361,8 +363,8 @@ module Mail #:nodoc:
361
363
  # Capitalizes the first letter of every word, when possible.
362
364
  #
363
365
  # Example:
364
- # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
365
- # "日本語".mb_chars.titleize # => "日本語"
366
+ # Mail::Multibyte.mb_chars("ÉL QUE SE ENTERÓ").titleize # => "Él Que Se Enteró"
367
+ # Mail::Multibyte.mb_chars("日本語").titleize # => "日本語"
366
368
  def titleize
367
369
  chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.apply_mapping $1, :uppercase_mapping })
368
370
  end
@@ -382,7 +384,7 @@ module Mail #:nodoc:
382
384
  #
383
385
  # Example:
384
386
  # 'é'.length # => 2
385
- # 'é'.mb_chars.decompose.to_s.length # => 3
387
+ # Mail::Multibyte.mb_chars('é').decompose.to_s.length # => 3
386
388
  def decompose
387
389
  chars(Unicode.decompose_codepoints(:canonical, Unicode.u_unpack(@wrapped_string)).pack('U*'))
388
390
  end
@@ -391,7 +393,7 @@ module Mail #:nodoc:
391
393
  #
392
394
  # Example:
393
395
  # 'é'.length # => 3
394
- # 'é'.mb_chars.compose.to_s.length # => 2
396
+ # Mail::Multibyte.mb_chars('é').compose.to_s.length # => 2
395
397
  def compose
396
398
  chars(Unicode.compose_codepoints(Unicode.u_unpack(@wrapped_string)).pack('U*'))
397
399
  end
@@ -399,8 +401,8 @@ module Mail #:nodoc:
399
401
  # Returns the number of grapheme clusters in the string.
400
402
  #
401
403
  # Example:
402
- # 'क्षि'.mb_chars.length # => 4
403
- # 'क्षि'.mb_chars.g_length # => 3
404
+ # Mail::Multibyte.mb_chars('क्षि').length # => 4
405
+ # Mail::Multibyte.mb_chars('क्षि').g_length # => 3
404
406
  def g_length
405
407
  Unicode.g_unpack(@wrapped_string).length
406
408
  end