mail 2.6.1 → 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 (179) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +92 -80
  4. data/lib/mail/attachments_list.rb +11 -5
  5. data/lib/mail/body.rb +81 -44
  6. data/lib/mail/check_delivery_params.rb +50 -10
  7. data/lib/mail/configuration.rb +3 -0
  8. data/lib/mail/{patterns.rb → constants.rb} +26 -6
  9. data/lib/mail/core_extensions/smtp.rb +20 -16
  10. data/lib/mail/core_extensions/string.rb +1 -27
  11. data/lib/mail/elements/address.rb +81 -93
  12. data/lib/mail/elements/address_list.rb +12 -29
  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 +20 -0
  32. data/lib/mail/encodings.rb +121 -82
  33. data/lib/mail/envelope.rb +2 -1
  34. data/lib/mail/field.rb +114 -62
  35. data/lib/mail/field_list.rb +2 -1
  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 +6 -11
  43. data/lib/mail/fields/common/common_message_id.rb +3 -2
  44. data/lib/mail/fields/common/parameter_hash.rb +5 -4
  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 +28 -10
  74. data/lib/mail/fields.rb +1 -0
  75. data/lib/mail/header.rb +18 -14
  76. data/lib/mail/indifferent_hash.rb +1 -0
  77. data/lib/mail/mail.rb +6 -11
  78. data/lib/mail/matchers/attachment_matchers.rb +29 -0
  79. data/lib/mail/matchers/has_sent_mail.rb +53 -9
  80. data/lib/mail/message.rb +99 -89
  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 -46
  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 +112 -13
  134. data/lib/mail/values/unicode_tables.dat +0 -0
  135. data/lib/mail/version.rb +8 -15
  136. data/lib/mail/version_specific/ruby_1_8.rb +52 -8
  137. data/lib/mail/version_specific/ruby_1_9.rb +143 -24
  138. data/lib/mail.rb +8 -14
  139. metadata +71 -81
  140. data/CHANGELOG.rdoc +0 -752
  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/VERSION +0 -4
  147. data/lib/mail/core_extensions/nil.rb +0 -19
  148. data/lib/mail/core_extensions/object.rb +0 -13
  149. data/lib/mail/core_extensions/string/access.rb +0 -145
  150. data/lib/mail/core_extensions/string/multibyte.rb +0 -78
  151. data/lib/mail/multibyte/exceptions.rb +0 -8
  152. data/lib/mail/parsers/ragel/common.rl +0 -184
  153. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  154. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  155. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  156. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  157. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  158. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  159. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  160. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  161. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  162. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  163. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  164. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  165. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  166. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2129
  167. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  168. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  169. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  170. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  171. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  172. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  173. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  174. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  175. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  176. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  177. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  178. data/lib/mail/parsers/ragel/ruby.rb +0 -39
  179. 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
@@ -46,7 +47,7 @@ module Mail
46
47
  # (i.e., a line with nothing preceding the CRLF).
47
48
  class Message
48
49
 
49
- include Patterns
50
+ include Constants
50
51
  include Utilities
51
52
 
52
53
  # ==Making an email
@@ -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!")
@@ -105,7 +114,7 @@ module Mail
105
114
  @html_part = nil
106
115
  @errors = nil
107
116
  @header = nil
108
- @charset = 'UTF-8'
117
+ @charset = self.class.default_charset
109
118
  @defaulted_charset = true
110
119
 
111
120
  @smtp_envelope_from = nil
@@ -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
@@ -207,8 +231,12 @@ module Mail
207
231
  # define a delivery_handler
208
232
  attr_accessor :raise_delivery_errors
209
233
 
234
+ def self.default_charset; @@default_charset; end
235
+ def self.default_charset=(charset); @@default_charset = charset; end
236
+ self.default_charset = 'UTF-8'
237
+
210
238
  def register_for_delivery_notification(observer)
211
- 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")
212
240
  Mail.register_observer(observer)
213
241
  end
214
242
 
@@ -220,7 +248,7 @@ module Mail
220
248
  Mail.inform_interceptors(self)
221
249
  end
222
250
 
223
- # Delivers an mail object.
251
+ # Delivers a mail object.
224
252
  #
225
253
  # Examples:
226
254
  #
@@ -355,13 +383,8 @@ module Mail
355
383
  if self.message_id && other.message_id
356
384
  self.encoded == other.encoded
357
385
  else
358
- self_message_id, other_message_id = self.message_id, other.message_id
359
- begin
360
- self.message_id, other.message_id = '<temp@test>', '<temp@test>'
361
- self.encoded == other.encoded
362
- ensure
363
- self.message_id, other.message_id = self_message_id, other_message_id
364
- end
386
+ dup.tap { |m| m.message_id = '<temp@test>' }.encoded ==
387
+ other.dup.tap { |m| m.message_id = '<temp@test>' }.encoded
365
388
  end
366
389
  end
367
390
 
@@ -1188,8 +1211,8 @@ module Mail
1188
1211
  def default( sym, val = nil )
1189
1212
  if val
1190
1213
  header[sym] = val
1191
- else
1192
- header[sym].default if header[sym]
1214
+ elsif field = header[sym]
1215
+ field.default
1193
1216
  end
1194
1217
  end
1195
1218
 
@@ -1235,14 +1258,13 @@ module Mail
1235
1258
  def body(value = nil)
1236
1259
  if value
1237
1260
  self.body = value
1238
- # add_encoding_to_body
1239
1261
  else
1240
1262
  process_body_raw if @body_raw
1241
1263
  @body
1242
1264
  end
1243
1265
  end
1244
1266
 
1245
- def body_encoding(value)
1267
+ def body_encoding(value = nil)
1246
1268
  if value.nil?
1247
1269
  body.encoding
1248
1270
  else
@@ -1251,7 +1273,7 @@ module Mail
1251
1273
  end
1252
1274
 
1253
1275
  def body_encoding=(value)
1254
- body.encoding = value
1276
+ body.encoding = value
1255
1277
  end
1256
1278
 
1257
1279
  # Returns the list of addresses this message should be sent to by
@@ -1411,11 +1433,11 @@ module Mail
1411
1433
  end
1412
1434
 
1413
1435
  def has_content_transfer_encoding?
1414
- header[:content_transfer_encoding] && header[:content_transfer_encoding].errors.blank?
1436
+ header[:content_transfer_encoding] && Utilities.blank?(header[:content_transfer_encoding].errors)
1415
1437
  end
1416
1438
 
1417
1439
  def has_transfer_encoding? # :nodoc:
1418
- 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}")
1419
1441
  has_content_transfer_encoding?
1420
1442
  end
1421
1443
 
@@ -1463,34 +1485,26 @@ module Mail
1463
1485
  if !body.empty?
1464
1486
  # Only give a warning if this isn't an attachment, has non US-ASCII and the user
1465
1487
  # has not specified an encoding explicitly.
1466
- if @defaulted_charset && body.raw_source.not_ascii_only? && !self.attachment?
1488
+ if @defaulted_charset && !body.raw_source.ascii_only? && !self.attachment?
1467
1489
  warning = "Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.\n"
1468
- STDERR.puts(warning)
1490
+ warn(warning)
1469
1491
  end
1470
1492
  header[:content_type].parameters['charset'] = @charset
1471
1493
  end
1472
1494
  end
1473
1495
 
1474
1496
  # Adds a content transfer encoding
1475
- #
1476
- # Otherwise raises a warning
1477
1497
  def add_content_transfer_encoding
1478
- if body.only_us_ascii?
1479
- header[:content_transfer_encoding] = '7bit'
1480
- else
1481
- warning = "Non US-ASCII detected and no content-transfer-encoding defined.\nDefaulting to 8bit, set your own if this is incorrect.\n"
1482
- STDERR.puts(warning)
1483
- header[:content_transfer_encoding] = '8bit'
1484
- end
1498
+ header[:content_transfer_encoding] ||= body.default_encoding
1485
1499
  end
1486
1500
 
1487
1501
  def add_transfer_encoding # :nodoc:
1488
- 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}")
1489
1503
  add_content_transfer_encoding
1490
1504
  end
1491
1505
 
1492
1506
  def transfer_encoding # :nodoc:
1493
- 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}")
1494
1508
  content_transfer_encoding
1495
1509
  end
1496
1510
 
@@ -1500,7 +1514,7 @@ module Mail
1500
1514
  end
1501
1515
 
1502
1516
  def message_content_type
1503
- 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}")
1504
1518
  mime_type
1505
1519
  end
1506
1520
 
@@ -1532,7 +1546,7 @@ module Mail
1532
1546
 
1533
1547
  # Returns the content type parameters
1534
1548
  def mime_parameters
1535
- 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')
1536
1550
  content_type_parameters
1537
1551
  end
1538
1552
 
@@ -1558,7 +1572,14 @@ module Mail
1558
1572
 
1559
1573
  # returns the part in a multipart/report email that has the content-type delivery-status
1560
1574
  def delivery_status_part
1561
- @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
1562
1583
  end
1563
1584
 
1564
1585
  def bounced?
@@ -1665,6 +1686,8 @@ module Mail
1665
1686
  def html_part=(msg)
1666
1687
  # Assign the html part and set multipart/alternative if there's a text part.
1667
1688
  if msg
1689
+ msg = Mail::Part.new(:body => msg) unless msg.kind_of?(Mail::Message)
1690
+
1668
1691
  @html_part = msg
1669
1692
  @html_part.content_type = 'text/html' unless @html_part.has_content_type?
1670
1693
  add_multipart_alternate_header if text_part
@@ -1687,6 +1710,8 @@ module Mail
1687
1710
  def text_part=(msg)
1688
1711
  # Assign the text part and set multipart/alternative if there's an html part.
1689
1712
  if msg
1713
+ msg = Mail::Part.new(:body => msg) unless msg.kind_of?(Mail::Message)
1714
+
1690
1715
  @text_part = msg
1691
1716
  @text_part.content_type = 'text/plain' unless @text_part.has_content_type?
1692
1717
  add_multipart_alternate_header if html_part
@@ -1705,7 +1730,7 @@ module Mail
1705
1730
 
1706
1731
  # Adds a part to the parts list or creates the part list
1707
1732
  def add_part(part)
1708
- if !body.multipart? && !self.body.decoded.blank?
1733
+ if !body.multipart? && !Utilities.blank?(self.body.decoded)
1709
1734
  @text_part = Mail::Part.new('Content-Type: text/plain;')
1710
1735
  @text_part.body = body.decoded
1711
1736
  self.body << @text_part
@@ -1761,14 +1786,14 @@ module Mail
1761
1786
  #
1762
1787
  # See also #attachments
1763
1788
  def add_file(values)
1764
- convert_to_multipart unless self.multipart? || self.body.decoded.blank?
1789
+ convert_to_multipart unless self.multipart? || Utilities.blank?(self.body.decoded)
1765
1790
  add_multipart_mixed_header
1766
1791
  if values.is_a?(String)
1767
1792
  basename = File.basename(values)
1768
1793
  filedata = File.open(values, 'rb') { |f| f.read }
1769
1794
  else
1770
1795
  basename = values[:filename]
1771
- filedata = values[:content] || File.open(values[:filename], 'rb') { |f| f.read }
1796
+ filedata = values
1772
1797
  end
1773
1798
  self.attachments[basename] = filedata
1774
1799
  end
@@ -1786,7 +1811,6 @@ module Mail
1786
1811
  # ready to send
1787
1812
  def ready_to_send!
1788
1813
  identify_and_set_transfer_encoding
1789
- parts.sort!([ "text/plain", "text/enriched", "text/html", "multipart/alternative" ])
1790
1814
  parts.each do |part|
1791
1815
  part.transport_encoding = transport_encoding
1792
1816
  part.ready_to_send!
@@ -1795,7 +1819,7 @@ module Mail
1795
1819
  end
1796
1820
 
1797
1821
  def encode!
1798
- 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.")
1799
1823
  ready_to_send!
1800
1824
  end
1801
1825
 
@@ -1811,16 +1835,13 @@ module Mail
1811
1835
  end
1812
1836
 
1813
1837
  def without_attachments!
1814
- return self unless has_attachments?
1815
-
1816
- parts.delete_if { |p| p.attachment? }
1817
- body_raw = if parts.empty?
1818
- ''
1819
- else
1820
- body.encoded
1821
- end
1838
+ if has_attachments?
1839
+ parts.delete_if { |p| p.attachment? }
1822
1840
 
1823
- @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
1824
1845
 
1825
1846
  self
1826
1847
  end
@@ -1853,7 +1874,7 @@ module Mail
1853
1874
  case
1854
1875
  when k == 'delivery_handler'
1855
1876
  begin
1856
- m.delivery_handler = Object.const_get(v) unless v.blank?
1877
+ m.delivery_handler = Object.const_get(v) unless Utilities.blank?(v)
1857
1878
  rescue NameError
1858
1879
  end
1859
1880
  when k == 'transport_encoding'
@@ -1963,6 +1984,8 @@ module Mail
1963
1984
 
1964
1985
  private
1965
1986
 
1987
+ HEADER_SEPARATOR = /#{Constants::CRLF}#{Constants::CRLF}/
1988
+
1966
1989
  # 2.1. General Description
1967
1990
  # A message consists of header fields (collectively called "the header
1968
1991
  # of the message") followed, optionally, by a body. The header is a
@@ -1970,19 +1993,14 @@ module Mail
1970
1993
  # this standard. The body is simply a sequence of characters that
1971
1994
  # follows the header and is separated from the header by an empty line
1972
1995
  # (i.e., a line with nothing preceding the CRLF).
1973
- #
1974
- # Additionally, I allow for the case where someone might have put whitespace
1975
- # on the "gap line"
1976
1996
  def parse_message
1977
- header_part, body_part = raw_source.lstrip.split(/#{CRLF}#{CRLF}|#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m, 2)
1997
+ header_part, body_part = raw_source.lstrip.split(HEADER_SEPARATOR, 2)
1978
1998
  self.header = header_part
1979
1999
  self.body = body_part
1980
2000
  end
1981
2001
 
1982
2002
  def raw_source=(value)
1983
- @raw_source = value.to_crlf
1984
- @raw_source.force_encoding("binary") if RUBY_VERSION >= "1.9.1"
1985
- @raw_source
2003
+ @raw_source = value
1986
2004
  end
1987
2005
 
1988
2006
  # see comments to body=. We take data and process it lazily
@@ -1994,11 +2012,9 @@ module Mail
1994
2012
  @body_raw = nil
1995
2013
  add_encoding_to_body
1996
2014
  when @body && @body.multipart?
1997
- @body << Mail::Part.new(value)
1998
- add_encoding_to_body
2015
+ self.text_part = value
1999
2016
  else
2000
2017
  @body_raw = value
2001
- # process_body_raw
2002
2018
  end
2003
2019
  end
2004
2020
 
@@ -2013,9 +2029,9 @@ module Mail
2013
2029
 
2014
2030
  def set_envelope_header
2015
2031
  raw_string = raw_source.to_s
2016
- 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)
2017
2033
  set_envelope(match_data[1])
2018
- self.raw_source = raw_string.sub(match_data[0], "")
2034
+ self.raw_source = raw_string.sub(match_data[0], "")
2019
2035
  end
2020
2036
  end
2021
2037
 
@@ -2023,6 +2039,13 @@ module Mail
2023
2039
  body.split!(boundary)
2024
2040
  end
2025
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
+
2026
2049
  def add_encoding_to_body
2027
2050
  if has_content_transfer_encoding?
2028
2051
  @body.encoding = content_transfer_encoding
@@ -2030,18 +2053,18 @@ module Mail
2030
2053
  end
2031
2054
 
2032
2055
  def identify_and_set_transfer_encoding
2033
- if body && body.multipart?
2034
- self.content_transfer_encoding = @transport_encoding
2035
- else
2036
- self.content_transfer_encoding = body.get_best_encoding(@transport_encoding)
2037
- 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
2038
2061
  end
2039
2062
 
2040
2063
  def add_required_fields
2041
2064
  add_required_message_fields
2042
2065
  add_multipart_mixed_header if body.multipart?
2043
2066
  add_content_type unless has_content_type?
2044
- add_charset unless has_charset?
2067
+ add_charset if text? && !has_charset?
2045
2068
  add_content_transfer_encoding unless has_content_transfer_encoding?
2046
2069
  end
2047
2070
 
@@ -2117,10 +2140,10 @@ module Mail
2117
2140
  content_disp_name = header[:content_disposition].filename rescue nil
2118
2141
  content_loc_name = header[:content_location].location rescue nil
2119
2142
  case
2120
- when content_type && content_type_name
2121
- filename = content_type_name
2122
2143
  when content_disposition && content_disp_name
2123
2144
  filename = content_disp_name
2145
+ when content_type && content_type_name
2146
+ filename = content_type_name
2124
2147
  when content_location && content_loc_name
2125
2148
  filename = content_loc_name
2126
2149
  else
@@ -2141,20 +2164,7 @@ module Mail
2141
2164
  end
2142
2165
 
2143
2166
  def decode_body_as_text
2144
- body_text = decode_body
2145
- if charset
2146
- if RUBY_VERSION < '1.9'
2147
- require 'iconv'
2148
- return Iconv.conv("UTF-8//TRANSLIT//IGNORE", charset, body_text)
2149
- else
2150
- if encoding = Encoding.find(charset) rescue nil
2151
- body_text.force_encoding(encoding)
2152
- return body_text.encode(Encoding::UTF_8, :undef => :replace, :invalid => :replace, :replace => '')
2153
- end
2154
- end
2155
- end
2156
- body_text
2167
+ Encodings.transcode_charset decode_body, charset, 'UTF-8'
2157
2168
  end
2158
-
2159
2169
  end
2160
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