mail 2.6.6 → 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 (160) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +74 -90
  4. data/lib/mail/attachments_list.rb +8 -4
  5. data/lib/mail/body.rb +50 -38
  6. data/lib/mail/check_delivery_params.rb +8 -6
  7. data/lib/mail/configuration.rb +2 -0
  8. data/lib/mail/constants.rb +1 -1
  9. data/lib/mail/core_extensions/smtp.rb +19 -16
  10. data/lib/mail/core_extensions/string.rb +0 -4
  11. data/lib/mail/elements/address.rb +28 -22
  12. data/lib/mail/elements/address_list.rb +10 -18
  13. data/lib/mail/elements/content_disposition_element.rb +8 -15
  14. data/lib/mail/elements/content_location_element.rb +5 -10
  15. data/lib/mail/elements/content_transfer_encoding_element.rb +5 -10
  16. data/lib/mail/elements/content_type_element.rb +8 -19
  17. data/lib/mail/elements/date_time_element.rb +6 -14
  18. data/lib/mail/elements/envelope_from_element.rb +14 -21
  19. data/lib/mail/elements/message_ids_element.rb +8 -12
  20. data/lib/mail/elements/mime_version_element.rb +6 -14
  21. data/lib/mail/elements/phrase_list.rb +6 -9
  22. data/lib/mail/elements/received_element.rb +9 -15
  23. data/lib/mail/encodings/7bit.rb +5 -15
  24. data/lib/mail/encodings/8bit.rb +2 -21
  25. data/lib/mail/encodings/base64.rb +11 -12
  26. data/lib/mail/encodings/binary.rb +3 -22
  27. data/lib/mail/encodings/identity.rb +24 -0
  28. data/lib/mail/encodings/quoted_printable.rb +6 -6
  29. data/lib/mail/encodings/transfer_encoding.rb +38 -29
  30. data/lib/mail/encodings/unix_to_unix.rb +3 -1
  31. data/lib/mail/encodings.rb +99 -43
  32. data/lib/mail/envelope.rb +1 -1
  33. data/lib/mail/field.rb +96 -59
  34. data/lib/mail/fields/bcc_field.rb +2 -2
  35. data/lib/mail/fields/cc_field.rb +1 -1
  36. data/lib/mail/fields/comments_field.rb +1 -1
  37. data/lib/mail/fields/common/common_address.rb +32 -7
  38. data/lib/mail/fields/common/common_field.rb +1 -10
  39. data/lib/mail/fields/common/parameter_hash.rb +1 -1
  40. data/lib/mail/fields/content_description_field.rb +1 -1
  41. data/lib/mail/fields/content_disposition_field.rb +3 -3
  42. data/lib/mail/fields/content_id_field.rb +2 -2
  43. data/lib/mail/fields/content_location_field.rb +1 -1
  44. data/lib/mail/fields/content_transfer_encoding_field.rb +1 -1
  45. data/lib/mail/fields/content_type_field.rb +4 -9
  46. data/lib/mail/fields/date_field.rb +2 -3
  47. data/lib/mail/fields/from_field.rb +1 -1
  48. data/lib/mail/fields/in_reply_to_field.rb +1 -1
  49. data/lib/mail/fields/keywords_field.rb +1 -1
  50. data/lib/mail/fields/message_id_field.rb +1 -1
  51. data/lib/mail/fields/mime_version_field.rb +1 -1
  52. data/lib/mail/fields/optional_field.rb +4 -1
  53. data/lib/mail/fields/received_field.rb +1 -1
  54. data/lib/mail/fields/references_field.rb +1 -1
  55. data/lib/mail/fields/reply_to_field.rb +1 -1
  56. data/lib/mail/fields/resent_bcc_field.rb +1 -1
  57. data/lib/mail/fields/resent_cc_field.rb +1 -1
  58. data/lib/mail/fields/resent_date_field.rb +0 -1
  59. data/lib/mail/fields/resent_from_field.rb +1 -1
  60. data/lib/mail/fields/resent_message_id_field.rb +1 -1
  61. data/lib/mail/fields/resent_sender_field.rb +1 -1
  62. data/lib/mail/fields/resent_to_field.rb +1 -1
  63. data/lib/mail/fields/return_path_field.rb +1 -1
  64. data/lib/mail/fields/sender_field.rb +1 -1
  65. data/lib/mail/fields/subject_field.rb +1 -1
  66. data/lib/mail/fields/to_field.rb +1 -1
  67. data/lib/mail/fields/unstructured_field.rb +21 -4
  68. data/lib/mail/header.rb +10 -8
  69. data/lib/mail/mail.rb +2 -10
  70. data/lib/mail/matchers/has_sent_mail.rb +21 -1
  71. data/lib/mail/message.rb +78 -68
  72. data/lib/mail/multibyte/chars.rb +29 -28
  73. data/lib/mail/multibyte/unicode.rb +10 -10
  74. data/lib/mail/multibyte.rb +64 -15
  75. data/lib/mail/network/delivery_methods/logger_delivery.rb +37 -0
  76. data/lib/mail/network/delivery_methods/sendmail.rb +8 -5
  77. data/lib/mail/network/delivery_methods/smtp.rb +58 -49
  78. data/lib/mail/network/delivery_methods/smtp_connection.rb +9 -1
  79. data/lib/mail/network/retriever_methods/imap.rb +18 -5
  80. data/lib/mail/network/retriever_methods/pop3.rb +3 -1
  81. data/lib/mail/network.rb +1 -0
  82. data/lib/mail/parser_tools.rb +15 -0
  83. data/lib/mail/parsers/address_lists_parser.rb +33207 -104
  84. data/lib/mail/parsers/address_lists_parser.rl +172 -0
  85. data/lib/mail/parsers/content_disposition_parser.rb +876 -49
  86. data/lib/mail/parsers/content_disposition_parser.rl +82 -0
  87. data/lib/mail/parsers/content_location_parser.rb +803 -23
  88. data/lib/mail/parsers/content_location_parser.rl +71 -0
  89. data/lib/mail/parsers/content_transfer_encoding_parser.rb +501 -19
  90. data/lib/mail/parsers/content_transfer_encoding_parser.rl +64 -0
  91. data/lib/mail/parsers/content_type_parser.rb +1023 -48
  92. data/lib/mail/parsers/content_type_parser.rl +83 -0
  93. data/lib/mail/parsers/date_time_parser.rb +870 -24
  94. data/lib/mail/parsers/date_time_parser.rl +62 -0
  95. data/lib/mail/parsers/envelope_from_parser.rb +3569 -34
  96. data/lib/mail/parsers/envelope_from_parser.rl +82 -0
  97. data/lib/mail/parsers/message_ids_parser.rb +2839 -25
  98. data/lib/mail/parsers/message_ids_parser.rl +82 -0
  99. data/lib/mail/parsers/mime_version_parser.rb +491 -26
  100. data/lib/mail/parsers/mime_version_parser.rl +61 -0
  101. data/lib/mail/parsers/phrase_lists_parser.rb +860 -18
  102. data/lib/mail/parsers/phrase_lists_parser.rl +83 -0
  103. data/lib/mail/parsers/received_parser.rb +8764 -37
  104. data/lib/mail/parsers/received_parser.rl +84 -0
  105. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  106. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  107. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  108. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  109. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  110. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  111. data/lib/mail/parsers/rfc5322.rl +59 -0
  112. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  113. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  114. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  115. data/lib/mail/parsers.rb +16 -24
  116. data/lib/mail/part.rb +3 -3
  117. data/lib/mail/parts_list.rb +5 -6
  118. data/lib/mail/utilities.rb +59 -28
  119. data/lib/mail/version.rb +2 -2
  120. data/lib/mail/version_specific/ruby_1_8.rb +40 -3
  121. data/lib/mail/version_specific/ruby_1_9.rb +61 -9
  122. data/lib/mail.rb +3 -16
  123. metadata +44 -53
  124. data/CHANGELOG.rdoc +0 -803
  125. data/CONTRIBUTING.md +0 -60
  126. data/Dependencies.txt +0 -2
  127. data/Gemfile +0 -14
  128. data/Rakefile +0 -29
  129. data/TODO.rdoc +0 -9
  130. data/lib/mail/core_extensions/string/access.rb +0 -146
  131. data/lib/mail/core_extensions/string/multibyte.rb +0 -79
  132. data/lib/mail/multibyte/exceptions.rb +0 -9
  133. data/lib/mail/parsers/ragel/common.rl +0 -185
  134. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  135. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  136. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  137. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  138. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  139. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  140. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  141. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  142. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  143. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  144. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  145. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  146. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  147. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2149
  148. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  149. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  150. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  151. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  152. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  153. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  154. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  155. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  156. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  157. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  158. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  159. data/lib/mail/parsers/ragel/ruby.rb +0 -40
  160. data/lib/mail/parsers/ragel.rb +0 -18
data/lib/mail/message.rb CHANGED
@@ -56,15 +56,23 @@ module Mail
56
56
  #
57
57
  # ===Making an email via a block
58
58
  #
59
- # mail = Mail.new do
60
- # from 'mikel@test.lindsaar.net'
61
- # to 'you@test.lindsaar.net'
62
- # subject 'This is a test email'
63
- # 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')
64
64
  # end
65
65
  #
66
66
  # mail.to_s #=> "From: mikel@test.lindsaar.net\r\nTo: you@...
67
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
+ #
68
76
  # ===Making an email via passing a string
69
77
  #
70
78
  # mail = Mail.new("To: mikel@test.lindsaar.net\r\nSubject: Hello\r\n\r\nHi there!")
@@ -129,8 +137,23 @@ module Mail
129
137
  init_with_string(args.flatten[0].to_s)
130
138
  end
131
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
132
151
  if block_given?
133
- 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
134
157
  end
135
158
 
136
159
  self
@@ -213,7 +236,7 @@ module Mail
213
236
  self.default_charset = 'UTF-8'
214
237
 
215
238
  def register_for_delivery_notification(observer)
216
- 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")
217
240
  Mail.register_observer(observer)
218
241
  end
219
242
 
@@ -225,7 +248,7 @@ module Mail
225
248
  Mail.inform_interceptors(self)
226
249
  end
227
250
 
228
- # Delivers an mail object.
251
+ # Delivers a mail object.
229
252
  #
230
253
  # Examples:
231
254
  #
@@ -360,13 +383,8 @@ module Mail
360
383
  if self.message_id && other.message_id
361
384
  self.encoded == other.encoded
362
385
  else
363
- self_message_id, other_message_id = self.message_id, other.message_id
364
- begin
365
- self.message_id, other.message_id = '<temp@test>', '<temp@test>'
366
- self.encoded == other.encoded
367
- ensure
368
- self.message_id, other.message_id = self_message_id, other_message_id
369
- end
386
+ dup.tap { |m| m.message_id = '<temp@test>' }.encoded ==
387
+ other.dup.tap { |m| m.message_id = '<temp@test>' }.encoded
370
388
  end
371
389
  end
372
390
 
@@ -1193,8 +1211,8 @@ module Mail
1193
1211
  def default( sym, val = nil )
1194
1212
  if val
1195
1213
  header[sym] = val
1196
- else
1197
- header[sym].default if header[sym]
1214
+ elsif field = header[sym]
1215
+ field.default
1198
1216
  end
1199
1217
  end
1200
1218
 
@@ -1240,14 +1258,13 @@ module Mail
1240
1258
  def body(value = nil)
1241
1259
  if value
1242
1260
  self.body = value
1243
- # add_encoding_to_body
1244
1261
  else
1245
1262
  process_body_raw if @body_raw
1246
1263
  @body
1247
1264
  end
1248
1265
  end
1249
1266
 
1250
- def body_encoding(value)
1267
+ def body_encoding(value = nil)
1251
1268
  if value.nil?
1252
1269
  body.encoding
1253
1270
  else
@@ -1256,7 +1273,7 @@ module Mail
1256
1273
  end
1257
1274
 
1258
1275
  def body_encoding=(value)
1259
- body.encoding = value
1276
+ body.encoding = value
1260
1277
  end
1261
1278
 
1262
1279
  # Returns the list of addresses this message should be sent to by
@@ -1420,7 +1437,7 @@ module Mail
1420
1437
  end
1421
1438
 
1422
1439
  def has_transfer_encoding? # :nodoc:
1423
- 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}")
1424
1441
  has_content_transfer_encoding?
1425
1442
  end
1426
1443
 
@@ -1468,34 +1485,26 @@ module Mail
1468
1485
  if !body.empty?
1469
1486
  # Only give a warning if this isn't an attachment, has non US-ASCII and the user
1470
1487
  # has not specified an encoding explicitly.
1471
- if @defaulted_charset && body.raw_source.not_ascii_only? && !self.attachment?
1488
+ if @defaulted_charset && !body.raw_source.ascii_only? && !self.attachment?
1472
1489
  warning = "Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.\n"
1473
- STDERR.puts(warning)
1490
+ warn(warning)
1474
1491
  end
1475
1492
  header[:content_type].parameters['charset'] = @charset
1476
1493
  end
1477
1494
  end
1478
1495
 
1479
1496
  # Adds a content transfer encoding
1480
- #
1481
- # Otherwise raises a warning
1482
1497
  def add_content_transfer_encoding
1483
- if body.only_us_ascii?
1484
- header[:content_transfer_encoding] = '7bit'
1485
- else
1486
- warning = "Non US-ASCII detected and no content-transfer-encoding defined.\nDefaulting to 8bit, set your own if this is incorrect.\n"
1487
- STDERR.puts(warning)
1488
- header[:content_transfer_encoding] = '8bit'
1489
- end
1498
+ header[:content_transfer_encoding] ||= body.default_encoding
1490
1499
  end
1491
1500
 
1492
1501
  def add_transfer_encoding # :nodoc:
1493
- 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}")
1494
1503
  add_content_transfer_encoding
1495
1504
  end
1496
1505
 
1497
1506
  def transfer_encoding # :nodoc:
1498
- 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}")
1499
1508
  content_transfer_encoding
1500
1509
  end
1501
1510
 
@@ -1505,7 +1514,7 @@ module Mail
1505
1514
  end
1506
1515
 
1507
1516
  def message_content_type
1508
- 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}")
1509
1518
  mime_type
1510
1519
  end
1511
1520
 
@@ -1537,7 +1546,7 @@ module Mail
1537
1546
 
1538
1547
  # Returns the content type parameters
1539
1548
  def mime_parameters
1540
- 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')
1541
1550
  content_type_parameters
1542
1551
  end
1543
1552
 
@@ -1563,7 +1572,14 @@ module Mail
1563
1572
 
1564
1573
  # returns the part in a multipart/report email that has the content-type delivery-status
1565
1574
  def delivery_status_part
1566
- @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
1567
1583
  end
1568
1584
 
1569
1585
  def bounced?
@@ -1778,9 +1794,6 @@ module Mail
1778
1794
  else
1779
1795
  basename = values[:filename]
1780
1796
  filedata = values
1781
- unless filedata[:content]
1782
- filedata = values.merge(:content=>File.open(values[:filename], 'rb') { |f| f.read })
1783
- end
1784
1797
  end
1785
1798
  self.attachments[basename] = filedata
1786
1799
  end
@@ -1798,7 +1811,6 @@ module Mail
1798
1811
  # ready to send
1799
1812
  def ready_to_send!
1800
1813
  identify_and_set_transfer_encoding
1801
- parts.sort!([ "text/plain", "text/enriched", "text/html", "multipart/alternative" ])
1802
1814
  parts.each do |part|
1803
1815
  part.transport_encoding = transport_encoding
1804
1816
  part.ready_to_send!
@@ -1807,7 +1819,7 @@ module Mail
1807
1819
  end
1808
1820
 
1809
1821
  def encode!
1810
- 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.")
1811
1823
  ready_to_send!
1812
1824
  end
1813
1825
 
@@ -1823,16 +1835,13 @@ module Mail
1823
1835
  end
1824
1836
 
1825
1837
  def without_attachments!
1826
- return self unless has_attachments?
1827
-
1828
- parts.delete_if { |p| p.attachment? }
1829
- body_raw = if parts.empty?
1830
- ''
1831
- else
1832
- body.encoded
1833
- end
1838
+ if has_attachments?
1839
+ parts.delete_if { |p| p.attachment? }
1834
1840
 
1835
- @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
1836
1845
 
1837
1846
  self
1838
1847
  end
@@ -1975,7 +1984,7 @@ module Mail
1975
1984
 
1976
1985
  private
1977
1986
 
1978
- HEADER_SEPARATOR = /#{CRLF}#{CRLF}|#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m
1987
+ HEADER_SEPARATOR = /#{Constants::CRLF}#{Constants::CRLF}/
1979
1988
 
1980
1989
  # 2.1. General Description
1981
1990
  # A message consists of header fields (collectively called "the header
@@ -1984,9 +1993,6 @@ module Mail
1984
1993
  # this standard. The body is simply a sequence of characters that
1985
1994
  # follows the header and is separated from the header by an empty line
1986
1995
  # (i.e., a line with nothing preceding the CRLF).
1987
- #
1988
- # Additionally, I allow for the case where someone might have put whitespace
1989
- # on the "gap line"
1990
1996
  def parse_message
1991
1997
  header_part, body_part = raw_source.lstrip.split(HEADER_SEPARATOR, 2)
1992
1998
  self.header = header_part
@@ -1994,8 +2000,7 @@ module Mail
1994
2000
  end
1995
2001
 
1996
2002
  def raw_source=(value)
1997
- value = value.dup.force_encoding(Encoding::BINARY) if RUBY_VERSION >= "1.9.1"
1998
- @raw_source = ::Mail::Utilities.to_crlf(value)
2003
+ @raw_source = value
1999
2004
  end
2000
2005
 
2001
2006
  # see comments to body=. We take data and process it lazily
@@ -2007,11 +2012,9 @@ module Mail
2007
2012
  @body_raw = nil
2008
2013
  add_encoding_to_body
2009
2014
  when @body && @body.multipart?
2010
- @body << Mail::Part.new(value)
2011
- add_encoding_to_body
2015
+ self.text_part = value
2012
2016
  else
2013
2017
  @body_raw = value
2014
- # process_body_raw
2015
2018
  end
2016
2019
  end
2017
2020
 
@@ -2026,7 +2029,7 @@ module Mail
2026
2029
 
2027
2030
  def set_envelope_header
2028
2031
  raw_string = raw_source.to_s
2029
- 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)
2030
2033
  set_envelope(match_data[1])
2031
2034
  self.raw_source = raw_string.sub(match_data[0], "")
2032
2035
  end
@@ -2036,6 +2039,13 @@ module Mail
2036
2039
  body.split!(boundary)
2037
2040
  end
2038
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
+
2039
2049
  def add_encoding_to_body
2040
2050
  if has_content_transfer_encoding?
2041
2051
  @body.encoding = content_transfer_encoding
@@ -2043,11 +2053,11 @@ module Mail
2043
2053
  end
2044
2054
 
2045
2055
  def identify_and_set_transfer_encoding
2046
- if body && body.multipart?
2047
- self.content_transfer_encoding = @transport_encoding
2048
- else
2049
- self.content_transfer_encoding = body.get_best_encoding(@transport_encoding)
2050
- 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
2051
2061
  end
2052
2062
 
2053
2063
  def add_required_fields
@@ -2130,10 +2140,10 @@ module Mail
2130
2140
  content_disp_name = header[:content_disposition].filename rescue nil
2131
2141
  content_loc_name = header[:content_location].location rescue nil
2132
2142
  case
2133
- when content_type && content_type_name
2134
- filename = content_type_name
2135
2143
  when content_disposition && content_disp_name
2136
2144
  filename = content_disp_name
2145
+ when content_type && content_type_name
2146
+ filename = content_type_name
2137
2147
  when content_location && content_loc_name
2138
2148
  filename = content_loc_name
2139
2149
  else
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+ require 'mail/multibyte/unicode'
3
4
 
4
5
  module Mail #:nodoc:
5
6
  module Multibyte #:nodoc:
@@ -40,7 +41,7 @@ module Mail #:nodoc:
40
41
  if RUBY_VERSION >= "1.9"
41
42
  # Creates a new Chars instance by wrapping _string_.
42
43
  def initialize(string)
43
- @wrapped_string = string
44
+ @wrapped_string = string.dup
44
45
  @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
45
46
  end
46
47
  else
@@ -103,7 +104,7 @@ module Mail #:nodoc:
103
104
  # Returns a new Chars object containing the _other_ object concatenated to the string.
104
105
  #
105
106
  # Example:
106
- # ('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"
107
108
  def +(other)
108
109
  chars(@wrapped_string + other)
109
110
  end
@@ -111,7 +112,7 @@ module Mail #:nodoc:
111
112
  # Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
112
113
  #
113
114
  # Example:
114
- # 'Café périferôl'.mb_chars =~ /ô/ # => 12
115
+ # Mail::Multibyte.mb_chars('Café périferôl') =~ /ô/ # => 12
115
116
  def =~(other)
116
117
  translate_offset(@wrapped_string =~ other)
117
118
  end
@@ -119,7 +120,7 @@ module Mail #:nodoc:
119
120
  # Inserts the passed string at specified codepoint offsets.
120
121
  #
121
122
  # Example:
122
- # '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"
123
124
  def insert(offset, fragment)
124
125
  unpacked = Unicode.u_unpack(@wrapped_string)
125
126
  unless offset > unpacked.length
@@ -135,7 +136,7 @@ module Mail #:nodoc:
135
136
  # Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
136
137
  #
137
138
  # Example:
138
- # 'Café'.mb_chars.include?('é') # => true
139
+ # Mail::Multibyte.mb_chars('Café').include?('é') # => true
139
140
  def include?(other)
140
141
  # We have to redefine this method because Enumerable defines it.
141
142
  @wrapped_string.include?(other)
@@ -144,8 +145,8 @@ module Mail #:nodoc:
144
145
  # Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
145
146
  #
146
147
  # Example:
147
- # 'Café périferôl'.mb_chars.index('ô') # => 12
148
- # '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
149
150
  def index(needle, offset=0)
150
151
  wrapped_offset = first(offset).wrapped_string.length
151
152
  index = @wrapped_string.index(needle, wrapped_offset)
@@ -157,8 +158,8 @@ module Mail #:nodoc:
157
158
  # string. Returns +nil+ if _needle_ isn't found.
158
159
  #
159
160
  # Example:
160
- # 'Café périferôl'.mb_chars.rindex('é') # => 6
161
- # '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
162
163
  def rindex(needle, offset=nil)
163
164
  offset ||= length
164
165
  wrapped_offset = first(offset).wrapped_string.length
@@ -190,7 +191,7 @@ module Mail #:nodoc:
190
191
  # Returns the codepoint of the first character in the string.
191
192
  #
192
193
  # Example:
193
- # 'こんにちは'.mb_chars.ord # => 12371
194
+ # Mail::Multibyte.mb_chars('こんにちは').ord # => 12371
194
195
  def ord
195
196
  Unicode.u_unpack(@wrapped_string)[0]
196
197
  end
@@ -199,10 +200,10 @@ module Mail #:nodoc:
199
200
  #
200
201
  # Example:
201
202
  #
202
- # "¾ cup".mb_chars.rjust(8).to_s
203
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8).to_s
203
204
  # # => " ¾ cup"
204
205
  #
205
- # "¾ 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
206
207
  # # => "   ¾ cup"
207
208
  def rjust(integer, padstr=' ')
208
209
  justify(integer, :right, padstr)
@@ -212,10 +213,10 @@ module Mail #:nodoc:
212
213
  #
213
214
  # Example:
214
215
  #
215
- # "¾ cup".mb_chars.rjust(8).to_s
216
+ # Mail::Multibyte.mb_chars("¾ cup").rjust(8).to_s
216
217
  # # => "¾ cup "
217
218
  #
218
- # "¾ 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
219
220
  # # => "¾ cup   "
220
221
  def ljust(integer, padstr=' ')
221
222
  justify(integer, :left, padstr)
@@ -225,10 +226,10 @@ module Mail #:nodoc:
225
226
  #
226
227
  # Example:
227
228
  #
228
- # "¾ cup".mb_chars.center(8).to_s
229
+ # Mail::Multibyte.mb_chars("¾ cup").center(8).to_s
229
230
  # # => " ¾ cup "
230
231
  #
231
- # "¾ 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
232
233
  # # => " ¾ cup  "
233
234
  def center(integer, padstr=' ')
234
235
  justify(integer, :center, padstr)
@@ -244,7 +245,7 @@ module Mail #:nodoc:
244
245
  # instances instead of String. This makes chaining methods easier.
245
246
  #
246
247
  # Example:
247
- # '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"]
248
249
  def split(*args)
249
250
  @wrapped_string.split(*args).map { |i| i.mb_chars }
250
251
  end
@@ -292,7 +293,7 @@ module Mail #:nodoc:
292
293
  # Reverses all characters in the string.
293
294
  #
294
295
  # Example:
295
- # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
296
+ # Mail::Multibyte.mb_chars('Café').reverse.to_s # => 'éfaC'
296
297
  def reverse
297
298
  chars(Unicode.g_unpack(@wrapped_string).reverse.flatten.pack('U*'))
298
299
  end
@@ -301,7 +302,7 @@ module Mail #:nodoc:
301
302
  # character.
302
303
  #
303
304
  # Example:
304
- # 'こんにちは'.mb_chars.slice(2..3).to_s # => "にち"
305
+ # Mail::Multibyte.mb_chars('こんにちは').slice(2..3).to_s # => "にち"
305
306
  def slice(*args)
306
307
  if args.size > 2
307
308
  raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
@@ -338,7 +339,7 @@ module Mail #:nodoc:
338
339
  # Convert characters in the string to uppercase.
339
340
  #
340
341
  # Example:
341
- # '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 ?"
342
343
  def upcase
343
344
  chars(Unicode.apply_mapping(@wrapped_string, :uppercase_mapping))
344
345
  end
@@ -346,7 +347,7 @@ module Mail #:nodoc:
346
347
  # Convert characters in the string to lowercase.
347
348
  #
348
349
  # Example:
349
- # '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"
350
351
  def downcase
351
352
  chars(Unicode.apply_mapping(@wrapped_string, :lowercase_mapping))
352
353
  end
@@ -354,7 +355,7 @@ module Mail #:nodoc:
354
355
  # Converts the first character to uppercase and the remainder to lowercase.
355
356
  #
356
357
  # Example:
357
- # 'über'.mb_chars.capitalize.to_s # => "Über"
358
+ # Mail::Multibyte.mb_chars('über').capitalize.to_s # => "Über"
358
359
  def capitalize
359
360
  (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
360
361
  end
@@ -362,8 +363,8 @@ module Mail #:nodoc:
362
363
  # Capitalizes the first letter of every word, when possible.
363
364
  #
364
365
  # Example:
365
- # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
366
- # "日本語".mb_chars.titleize # => "日本語"
366
+ # Mail::Multibyte.mb_chars("ÉL QUE SE ENTERÓ").titleize # => "Él Que Se Enteró"
367
+ # Mail::Multibyte.mb_chars("日本語").titleize # => "日本語"
367
368
  def titleize
368
369
  chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.apply_mapping $1, :uppercase_mapping })
369
370
  end
@@ -383,7 +384,7 @@ module Mail #:nodoc:
383
384
  #
384
385
  # Example:
385
386
  # 'é'.length # => 2
386
- # 'é'.mb_chars.decompose.to_s.length # => 3
387
+ # Mail::Multibyte.mb_chars('é').decompose.to_s.length # => 3
387
388
  def decompose
388
389
  chars(Unicode.decompose_codepoints(:canonical, Unicode.u_unpack(@wrapped_string)).pack('U*'))
389
390
  end
@@ -392,7 +393,7 @@ module Mail #:nodoc:
392
393
  #
393
394
  # Example:
394
395
  # 'é'.length # => 3
395
- # 'é'.mb_chars.compose.to_s.length # => 2
396
+ # Mail::Multibyte.mb_chars('é').compose.to_s.length # => 2
396
397
  def compose
397
398
  chars(Unicode.compose_codepoints(Unicode.u_unpack(@wrapped_string)).pack('U*'))
398
399
  end
@@ -400,8 +401,8 @@ module Mail #:nodoc:
400
401
  # Returns the number of grapheme clusters in the string.
401
402
  #
402
403
  # Example:
403
- # 'क्षि'.mb_chars.length # => 4
404
- # 'क्षि'.mb_chars.g_length # => 3
404
+ # Mail::Multibyte.mb_chars('क्षि').length # => 4
405
+ # Mail::Multibyte.mb_chars('क्षि').g_length # => 3
405
406
  def g_length
406
407
  Unicode.g_unpack(@wrapped_string).length
407
408
  end
@@ -302,16 +302,16 @@ module Mail
302
302
  # See http://www.unicode.org/reports/tr15, Table 1
303
303
  codepoints = u_unpack(string)
304
304
  case form
305
- when :d
306
- reorder_characters(decompose_codepoints(:canonical, codepoints))
307
- when :c
308
- compose_codepoints(reorder_characters(decompose_codepoints(:canonical, codepoints)))
309
- when :kd
310
- reorder_characters(decompose_codepoints(:compatability, codepoints))
311
- when :kc
312
- compose_codepoints(reorder_characters(decompose_codepoints(:compatability, codepoints)))
313
- else
314
- raise ArgumentError, "#{form} is not a valid normalization variant", caller
305
+ when :d
306
+ reorder_characters(decompose_codepoints(:canonical, codepoints))
307
+ when :c
308
+ compose_codepoints(reorder_characters(decompose_codepoints(:canonical, codepoints)))
309
+ when :kd
310
+ reorder_characters(decompose_codepoints(:compatability, codepoints))
311
+ when :kc
312
+ compose_codepoints(reorder_characters(decompose_codepoints(:compatability, codepoints)))
313
+ else
314
+ raise ArgumentError, "#{form} is not a valid normalization variant", caller
315
315
  end.pack('U*')
316
316
  end
317
317
 
@@ -1,24 +1,73 @@
1
1
  # encoding: utf-8
2
2
  # frozen_string_literal: true
3
+ require 'mail/multibyte/chars'
4
+
3
5
  module Mail #:nodoc:
4
6
  module Multibyte
5
- require 'mail/multibyte/exceptions'
6
- require 'mail/multibyte/chars'
7
- require 'mail/multibyte/unicode'
7
+ # Raised when a problem with the encoding was found.
8
+ class EncodingError < StandardError; end
8
9
 
9
- # The proxy class returned when calling mb_chars. You can use this accessor to configure your own proxy
10
- # class so you can support other encodings. See the Mail::Multibyte::Chars implementation for
11
- # an example how to do this.
12
- #
13
- # Example:
14
- # Mail::Multibyte.proxy_class = CharsForUTF32
15
- def self.proxy_class=(klass)
16
- @proxy_class = klass
10
+ class << self
11
+ # The proxy class returned when calling mb_chars. You can use this accessor to configure your own proxy
12
+ # class so you can support other encodings. See the Mail::Multibyte::Chars implementation for
13
+ # an example how to do this.
14
+ #
15
+ # Example:
16
+ # Mail::Multibyte.proxy_class = CharsForUTF32
17
+ attr_accessor :proxy_class
17
18
  end
18
19
 
19
- # Returns the current proxy class
20
- def self.proxy_class
21
- @proxy_class ||= Mail::Multibyte::Chars
20
+ self.proxy_class = Mail::Multibyte::Chars
21
+
22
+ if RUBY_VERSION >= "1.9"
23
+ # == Multibyte proxy
24
+ #
25
+ # +mb_chars+ is a multibyte safe proxy for string methods.
26
+ #
27
+ # In Ruby 1.8 and older it creates and returns an instance of the Mail::Multibyte::Chars class which
28
+ # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
29
+ # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsuled string.
30
+ #
31
+ # name = 'Claus Müller'
32
+ # name.reverse # => "rell??M sualC"
33
+ # name.length # => 13
34
+ #
35
+ # name.mb_chars.reverse.to_s # => "rellüM sualC"
36
+ # name.mb_chars.length # => 12
37
+ #
38
+ # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
39
+ # it becomes easy to run one version of your code on multiple Ruby versions.
40
+ #
41
+ # == Method chaining
42
+ #
43
+ # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
44
+ # method chaining on the result of any of these methods.
45
+ #
46
+ # name.mb_chars.reverse.length # => 12
47
+ #
48
+ # == Interoperability and configuration
49
+ #
50
+ # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
51
+ # String and Char work like expected. The bang! methods change the internal string representation in the Chars
52
+ # object. Interoperability problems can be resolved easily with a +to_s+ call.
53
+ #
54
+ # For more information about the methods defined on the Chars proxy see Mail::Multibyte::Chars. For
55
+ # information about how to change the default Multibyte behaviour see Mail::Multibyte.
56
+ def self.mb_chars(str)
57
+ if proxy_class.consumes?(str)
58
+ proxy_class.new(str)
59
+ else
60
+ str
61
+ end
62
+ end
63
+ else
64
+ def self.mb_chars(str)
65
+ if proxy_class.wants?(str)
66
+ proxy_class.new(str)
67
+ else
68
+ str
69
+ end
70
+ end
22
71
  end
23
72
 
24
73
  # Regular expressions that describe valid byte sequences for a character
@@ -40,4 +89,4 @@ module Mail #:nodoc:
40
89
  end
41
90
  end
42
91
 
43
- require 'mail/multibyte/utils'
92
+ require 'mail/multibyte/utils'