mail 2.5.2 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +78 -0
  3. data/CONTRIBUTING.md +17 -4
  4. data/Dependencies.txt +0 -1
  5. data/Gemfile +10 -21
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +50 -29
  8. data/Rakefile +11 -22
  9. data/VERSION +4 -0
  10. data/lib/mail/attachments_list.rb +2 -2
  11. data/lib/mail/body.rb +5 -5
  12. data/lib/mail/check_delivery_params.rb +12 -22
  13. data/lib/mail/core_extensions/object.rb +8 -8
  14. data/lib/mail/core_extensions/smtp.rb +12 -13
  15. data/lib/mail/core_extensions/string.rb +16 -6
  16. data/lib/mail/elements/address.rb +43 -79
  17. data/lib/mail/elements/address_list.rb +19 -42
  18. data/lib/mail/elements/content_disposition_element.rb +3 -7
  19. data/lib/mail/elements/content_location_element.rb +2 -6
  20. data/lib/mail/elements/content_transfer_encoding_element.rb +3 -10
  21. data/lib/mail/elements/content_type_element.rb +4 -8
  22. data/lib/mail/elements/date_time_element.rb +3 -7
  23. data/lib/mail/elements/envelope_from_element.rb +18 -13
  24. data/lib/mail/elements/message_ids_element.rb +1 -6
  25. data/lib/mail/elements/mime_version_element.rb +3 -7
  26. data/lib/mail/elements/phrase_list.rb +2 -7
  27. data/lib/mail/elements/received_element.rb +3 -7
  28. data/lib/mail/encodings/quoted_printable.rb +4 -3
  29. data/lib/mail/encodings.rb +60 -28
  30. data/lib/mail/envelope.rb +0 -5
  31. data/lib/mail/field.rb +63 -28
  32. data/lib/mail/field_list.rb +18 -18
  33. data/lib/mail/fields/bcc_field.rb +2 -2
  34. data/lib/mail/fields/cc_field.rb +2 -2
  35. data/lib/mail/fields/comments_field.rb +1 -1
  36. data/lib/mail/fields/common/common_address.rb +26 -21
  37. data/lib/mail/fields/common/common_date.rb +0 -7
  38. data/lib/mail/fields/common/common_field.rb +5 -5
  39. data/lib/mail/fields/content_id_field.rb +1 -2
  40. data/lib/mail/fields/content_transfer_encoding_field.rb +2 -8
  41. data/lib/mail/fields/content_type_field.rb +1 -1
  42. data/lib/mail/fields/date_field.rb +14 -14
  43. data/lib/mail/fields/from_field.rb +2 -2
  44. data/lib/mail/fields/in_reply_to_field.rb +2 -1
  45. data/lib/mail/fields/message_id_field.rb +2 -3
  46. data/lib/mail/fields/references_field.rb +2 -1
  47. data/lib/mail/fields/reply_to_field.rb +2 -2
  48. data/lib/mail/fields/resent_bcc_field.rb +2 -2
  49. data/lib/mail/fields/resent_cc_field.rb +2 -2
  50. data/lib/mail/fields/resent_from_field.rb +2 -2
  51. data/lib/mail/fields/resent_sender_field.rb +3 -3
  52. data/lib/mail/fields/resent_to_field.rb +2 -2
  53. data/lib/mail/fields/sender_field.rb +8 -8
  54. data/lib/mail/fields/to_field.rb +2 -2
  55. data/lib/mail/fields/unstructured_field.rb +8 -2
  56. data/lib/mail/header.rb +13 -23
  57. data/lib/mail/mail.rb +12 -0
  58. data/lib/mail/matchers/has_sent_mail.rb +34 -1
  59. data/lib/mail/message.rb +151 -48
  60. data/lib/mail/multibyte/chars.rb +2 -2
  61. data/lib/mail/multibyte/unicode.rb +6 -4
  62. data/lib/mail/network/delivery_methods/exim.rb +1 -6
  63. data/lib/mail/network/delivery_methods/file_delivery.rb +1 -1
  64. data/lib/mail/network/delivery_methods/sendmail.rb +32 -10
  65. data/lib/mail/network/delivery_methods/smtp.rb +31 -34
  66. data/lib/mail/network/delivery_methods/smtp_connection.rb +6 -6
  67. data/lib/mail/network/delivery_methods/test_mailer.rb +2 -2
  68. data/lib/mail/network/retriever_methods/imap.rb +18 -13
  69. data/lib/mail/parsers/address_lists_parser.rb +132 -0
  70. data/lib/mail/parsers/content_disposition_parser.rb +67 -0
  71. data/lib/mail/parsers/content_location_parser.rb +35 -0
  72. data/lib/mail/parsers/content_transfer_encoding_parser.rb +33 -0
  73. data/lib/mail/parsers/content_type_parser.rb +64 -0
  74. data/lib/mail/parsers/date_time_parser.rb +36 -0
  75. data/lib/mail/parsers/envelope_from_parser.rb +45 -0
  76. data/lib/mail/parsers/message_ids_parser.rb +39 -0
  77. data/lib/mail/parsers/mime_version_parser.rb +41 -0
  78. data/lib/mail/parsers/phrase_lists_parser.rb +33 -0
  79. data/lib/mail/parsers/ragel/common.rl +184 -0
  80. data/lib/mail/parsers/ragel/date_time.rl +30 -0
  81. data/lib/mail/parsers/ragel/parser_info.rb +61 -0
  82. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +14864 -0
  83. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +37 -0
  84. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +751 -0
  85. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +37 -0
  86. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +614 -0
  87. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +37 -0
  88. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +447 -0
  89. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +37 -0
  90. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +825 -0
  91. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +37 -0
  92. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +817 -0
  93. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +37 -0
  94. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +2129 -0
  95. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +37 -0
  96. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +1570 -0
  97. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +37 -0
  98. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +440 -0
  99. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +37 -0
  100. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +564 -0
  101. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +37 -0
  102. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +51 -0
  103. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +5144 -0
  104. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +37 -0
  105. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +37 -0
  106. data/lib/mail/parsers/ragel/ruby.rb +29 -0
  107. data/lib/mail/parsers/ragel.rb +17 -0
  108. data/lib/mail/parsers/received_parser.rb +47 -0
  109. data/lib/mail/parsers.rb +26 -0
  110. data/lib/mail/part.rb +6 -2
  111. data/lib/mail/parts_list.rb +5 -3
  112. data/lib/mail/patterns.rb +4 -1
  113. data/lib/mail/utilities.rb +28 -18
  114. data/lib/mail/version.rb +1 -1
  115. data/lib/mail/version_specific/ruby_1_8.rb +6 -2
  116. data/lib/mail/version_specific/ruby_1_9.rb +61 -21
  117. data/lib/mail.rb +1 -2
  118. metadata +112 -73
  119. data/lib/VERSION +0 -4
  120. data/lib/load_parsers.rb +0 -41
  121. data/lib/mail/core_extensions/shell_escape.rb +0 -56
  122. data/lib/mail/parsers/address_lists.rb +0 -64
  123. data/lib/mail/parsers/address_lists.treetop +0 -19
  124. data/lib/mail/parsers/content_disposition.rb +0 -535
  125. data/lib/mail/parsers/content_disposition.treetop +0 -46
  126. data/lib/mail/parsers/content_location.rb +0 -139
  127. data/lib/mail/parsers/content_location.treetop +0 -20
  128. data/lib/mail/parsers/content_transfer_encoding.rb +0 -162
  129. data/lib/mail/parsers/content_transfer_encoding.treetop +0 -20
  130. data/lib/mail/parsers/content_type.rb +0 -967
  131. data/lib/mail/parsers/content_type.treetop +0 -68
  132. data/lib/mail/parsers/date_time.rb +0 -114
  133. data/lib/mail/parsers/date_time.treetop +0 -11
  134. data/lib/mail/parsers/envelope_from.rb +0 -194
  135. data/lib/mail/parsers/envelope_from.treetop +0 -32
  136. data/lib/mail/parsers/message_ids.rb +0 -45
  137. data/lib/mail/parsers/message_ids.treetop +0 -15
  138. data/lib/mail/parsers/mime_version.rb +0 -144
  139. data/lib/mail/parsers/mime_version.treetop +0 -19
  140. data/lib/mail/parsers/phrase_lists.rb +0 -45
  141. data/lib/mail/parsers/phrase_lists.treetop +0 -15
  142. data/lib/mail/parsers/received.rb +0 -71
  143. data/lib/mail/parsers/received.treetop +0 -11
  144. data/lib/mail/parsers/rfc2045.rb +0 -464
  145. data/lib/mail/parsers/rfc2045.treetop +0 -36
  146. data/lib/mail/parsers/rfc2822.rb +0 -5397
  147. data/lib/mail/parsers/rfc2822.treetop +0 -410
  148. data/lib/mail/parsers/rfc2822_obsolete.rb +0 -3768
  149. data/lib/mail/parsers/rfc2822_obsolete.treetop +0 -241
  150. data/lib/tasks/corpus.rake +0 -125
  151. data/lib/tasks/treetop.rake +0 -10
data/lib/mail/message.rb CHANGED
@@ -108,6 +108,9 @@ module Mail
108
108
  @charset = 'UTF-8'
109
109
  @defaulted_charset = true
110
110
 
111
+ @smtp_envelope_from = nil
112
+ @smtp_envelope_to = nil
113
+
111
114
  @perform_deliveries = true
112
115
  @raise_delivery_errors = true
113
116
 
@@ -122,7 +125,7 @@ module Mail
122
125
  if args.flatten.first.respond_to?(:each_pair)
123
126
  init_with_hash(args.flatten.first)
124
127
  else
125
- init_with_string(args.flatten[0].to_s.strip)
128
+ init_with_string(args.flatten[0].to_s)
126
129
  end
127
130
 
128
131
  if block_given?
@@ -169,7 +172,7 @@ module Mail
169
172
  # obj.mail.deliver
170
173
  #
171
174
  # Would cause Mail to call obj.deliver_mail passing itself as a parameter,
172
- # which then can just yield and let Mail do it's own private do_delivery
175
+ # which then can just yield and let Mail do its own private do_delivery
173
176
  # method.
174
177
  attr_accessor :delivery_handler
175
178
 
@@ -237,7 +240,7 @@ module Mail
237
240
  # This method bypasses checking perform_deliveries and raise_delivery_errors,
238
241
  # so use with caution.
239
242
  #
240
- # It still however fires off the intercepters and calls the observers callbacks if they are defined.
243
+ # It still however fires off the interceptors and calls the observers callbacks if they are defined.
241
244
  #
242
245
  # Returns self
243
246
  def deliver!
@@ -350,17 +353,23 @@ module Mail
350
353
  return false unless other.respond_to?(:encoded)
351
354
 
352
355
  if self.message_id && other.message_id
353
- result = (self.encoded == other.encoded)
356
+ self.encoded == other.encoded
354
357
  else
355
358
  self_message_id, other_message_id = self.message_id, other.message_id
356
- self.message_id, other.message_id = '<temp@test>', '<temp@test>'
357
- result = self.encoded == other.encoded
358
- self.message_id = "<#{self_message_id}>" if self_message_id
359
- other.message_id = "<#{other_message_id}>" if other_message_id
360
- result
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
361
365
  end
362
366
  end
363
367
 
368
+ def initialize_copy(original)
369
+ super
370
+ @header = @header.dup
371
+ end
372
+
364
373
  # Provides access to the raw source of the message as it was when it
365
374
  # was instantiated. This is set at initialization and so is untouched
366
375
  # by the parsers or decoder / encoders
@@ -938,8 +947,8 @@ module Mail
938
947
  #
939
948
  # Example:
940
949
  #
941
- # mail.sender = 'Mikel <mikel@test.lindsaar.net>'
942
- # mail.sender #=> 'mikel@test.lindsaar.net'
950
+ # mail.resent_sender = 'Mikel <mikel@test.lindsaar.net>'
951
+ # mail.resent_sender #=> 'mikel@test.lindsaar.net'
943
952
  def resent_sender=( val )
944
953
  header[:resent_sender] = val
945
954
  end
@@ -1023,6 +1032,82 @@ module Mail
1023
1032
  header[:sender] = val
1024
1033
  end
1025
1034
 
1035
+ # Returns the SMTP Envelope From value of the mail object, as a single
1036
+ # string of an address spec.
1037
+ #
1038
+ # Defaults to Return-Path, Sender, or the first From address.
1039
+ #
1040
+ # Example:
1041
+ #
1042
+ # mail.smtp_envelope_from = 'Mikel <mikel@test.lindsaar.net>'
1043
+ # mail.smtp_envelope_from #=> 'mikel@test.lindsaar.net'
1044
+ #
1045
+ # Also allows you to set the value by passing a value as a parameter
1046
+ #
1047
+ # Example:
1048
+ #
1049
+ # mail.smtp_envelope_from 'Mikel <mikel@test.lindsaar.net>'
1050
+ # mail.smtp_envelope_from #=> 'mikel@test.lindsaar.net'
1051
+ def smtp_envelope_from( val = nil )
1052
+ if val
1053
+ self.smtp_envelope_from = val
1054
+ else
1055
+ @smtp_envelope_from || return_path || sender || from_addrs.first
1056
+ end
1057
+ end
1058
+
1059
+ # Sets the From address on the SMTP Envelope.
1060
+ #
1061
+ # Example:
1062
+ #
1063
+ # mail.smtp_envelope_from = 'Mikel <mikel@test.lindsaar.net>'
1064
+ # mail.smtp_envelope_from #=> 'mikel@test.lindsaar.net'
1065
+ def smtp_envelope_from=( val )
1066
+ @smtp_envelope_from = val
1067
+ end
1068
+
1069
+ # Returns the SMTP Envelope To value of the mail object.
1070
+ #
1071
+ # Defaults to #destinations: To, Cc, and Bcc addresses.
1072
+ #
1073
+ # Example:
1074
+ #
1075
+ # mail.smtp_envelope_to = 'Mikel <mikel@test.lindsaar.net>'
1076
+ # mail.smtp_envelope_to #=> 'mikel@test.lindsaar.net'
1077
+ #
1078
+ # Also allows you to set the value by passing a value as a parameter
1079
+ #
1080
+ # Example:
1081
+ #
1082
+ # mail.smtp_envelope_to ['Mikel <mikel@test.lindsaar.net>', 'Lindsaar <lindsaar@test.lindsaar.net>']
1083
+ # mail.smtp_envelope_to #=> ['mikel@test.lindsaar.net', 'lindsaar@test.lindsaar.net']
1084
+ def smtp_envelope_to( val = nil )
1085
+ if val
1086
+ self.smtp_envelope_to = val
1087
+ else
1088
+ @smtp_envelope_to || destinations
1089
+ end
1090
+ end
1091
+
1092
+ # Sets the To addresses on the SMTP Envelope.
1093
+ #
1094
+ # Example:
1095
+ #
1096
+ # mail.smtp_envelope_to = 'Mikel <mikel@test.lindsaar.net>'
1097
+ # mail.smtp_envelope_to #=> 'mikel@test.lindsaar.net'
1098
+ #
1099
+ # mail.smtp_envelope_to = ['Mikel <mikel@test.lindsaar.net>', 'Lindsaar <lindsaar@test.lindsaar.net>']
1100
+ # mail.smtp_envelope_to #=> ['mikel@test.lindsaar.net', 'lindsaar@test.lindsaar.net']
1101
+ def smtp_envelope_to=( val )
1102
+ @smtp_envelope_to =
1103
+ case val
1104
+ when Array, NilClass
1105
+ val
1106
+ else
1107
+ [val]
1108
+ end
1109
+ end
1110
+
1026
1111
  # Returns the decoded value of the subject field, as a single string.
1027
1112
  #
1028
1113
  # Example:
@@ -1309,7 +1394,7 @@ module Mail
1309
1394
  header.has_date?
1310
1395
  end
1311
1396
 
1312
- # Returns true if the message has a Date field, the field may or may
1397
+ # Returns true if the message has a Mime-Version field, the field may or may
1313
1398
  # not have a value, but the field exists or not.
1314
1399
  def has_mime_version?
1315
1400
  header.has_mime_version?
@@ -1515,8 +1600,8 @@ module Mail
1515
1600
  end
1516
1601
 
1517
1602
  # Returns an AttachmentsList object, which holds all of the attachments in
1518
- # the receiver object (either the entier email or a part within) and all
1519
- # of it's descendants.
1603
+ # the receiver object (either the entire email or a part within) and all
1604
+ # of its descendants.
1520
1605
  #
1521
1606
  # It also allows you to add attachments to the mail object directly, like so:
1522
1607
  #
@@ -1559,9 +1644,7 @@ module Mail
1559
1644
  # Accessor for html_part
1560
1645
  def html_part(&block)
1561
1646
  if block_given?
1562
- @html_part = Mail::Part.new(&block)
1563
- add_multipart_alternate_header unless html_part.blank?
1564
- add_part(@html_part)
1647
+ self.html_part = Mail::Part.new(:content_type => 'text/html', &block)
1565
1648
  else
1566
1649
  @html_part || find_first_mime_type('text/html')
1567
1650
  end
@@ -1570,9 +1653,7 @@ module Mail
1570
1653
  # Accessor for text_part
1571
1654
  def text_part(&block)
1572
1655
  if block_given?
1573
- @text_part = Mail::Part.new(&block)
1574
- add_multipart_alternate_header unless html_part.blank?
1575
- add_part(@text_part)
1656
+ self.text_part = Mail::Part.new(:content_type => 'text/plain', &block)
1576
1657
  else
1577
1658
  @text_part || find_first_mime_type('text/plain')
1578
1659
  end
@@ -1581,36 +1662,54 @@ module Mail
1581
1662
  # Helper to add a html part to a multipart/alternative email. If this and
1582
1663
  # text_part are both defined in a message, then it will be a multipart/alternative
1583
1664
  # message and set itself that way.
1584
- def html_part=(msg = nil)
1665
+ def html_part=(msg)
1666
+ # Assign the html part and set multipart/alternative if there's a text part.
1585
1667
  if msg
1586
1668
  @html_part = msg
1587
- else
1588
- @html_part = Mail::Part.new('Content-Type: text/html;')
1669
+ @html_part.content_type = 'text/html' unless @html_part.has_content_type?
1670
+ add_multipart_alternate_header if text_part
1671
+ add_part @html_part
1672
+
1673
+ # If nil, delete the html part and back out of multipart/alternative.
1674
+ elsif @html_part
1675
+ parts.delete_if { |p| p.object_id == @html_part.object_id }
1676
+ @html_part = nil
1677
+ if text_part
1678
+ self.content_type = nil
1679
+ body.boundary = nil
1680
+ end
1589
1681
  end
1590
- add_multipart_alternate_header unless text_part.blank?
1591
- add_part(@html_part)
1592
1682
  end
1593
1683
 
1594
1684
  # Helper to add a text part to a multipart/alternative email. If this and
1595
1685
  # html_part are both defined in a message, then it will be a multipart/alternative
1596
1686
  # message and set itself that way.
1597
- def text_part=(msg = nil)
1687
+ def text_part=(msg)
1688
+ # Assign the text part and set multipart/alternative if there's an html part.
1598
1689
  if msg
1599
1690
  @text_part = msg
1600
- else
1601
- @text_part = Mail::Part.new('Content-Type: text/plain;')
1691
+ @text_part.content_type = 'text/plain' unless @text_part.has_content_type?
1692
+ add_multipart_alternate_header if html_part
1693
+ add_part @text_part
1694
+
1695
+ # If nil, delete the text part and back out of multipart/alternative.
1696
+ elsif @text_part
1697
+ parts.delete_if { |p| p.object_id == @text_part.object_id }
1698
+ @text_part = nil
1699
+ if html_part
1700
+ self.content_type = nil
1701
+ body.boundary = nil
1702
+ end
1602
1703
  end
1603
- add_multipart_alternate_header unless html_part.blank?
1604
- add_part(@text_part)
1605
1704
  end
1606
1705
 
1607
1706
  # Adds a part to the parts list or creates the part list
1608
1707
  def add_part(part)
1609
1708
  if !body.multipart? && !self.body.decoded.blank?
1610
- @text_part = Mail::Part.new('Content-Type: text/plain;')
1611
- @text_part.body = body.decoded
1612
- self.body << @text_part
1613
- add_multipart_alternate_header
1709
+ @text_part = Mail::Part.new('Content-Type: text/plain;')
1710
+ @text_part.body = body.decoded
1711
+ self.body << @text_part
1712
+ add_multipart_alternate_header
1614
1713
  end
1615
1714
  add_boundary
1616
1715
  self.body << part
@@ -1646,7 +1745,7 @@ module Mail
1646
1745
  # m.add_file(:filename => 'filename.png', :content => File.read('/path/to/file.jpg'))
1647
1746
  #
1648
1747
  # Note also that if you add a file to an existing message, Mail will convert that message
1649
- # to a MIME multipart email, moving whatever plain text body you had into it's own text
1748
+ # to a MIME multipart email, moving whatever plain text body you had into its own text
1650
1749
  # plain part.
1651
1750
  #
1652
1751
  # Example:
@@ -1683,7 +1782,7 @@ module Mail
1683
1782
  self.body << text_part
1684
1783
  end
1685
1784
 
1686
- # Encodes the message, calls encode on all it's parts, gets an email message
1785
+ # Encodes the message, calls encode on all its parts, gets an email message
1687
1786
  # ready to send
1688
1787
  def ready_to_send!
1689
1788
  identify_and_set_transfer_encoding
@@ -1875,14 +1974,15 @@ module Mail
1875
1974
  # Additionally, I allow for the case where someone might have put whitespace
1876
1975
  # on the "gap line"
1877
1976
  def parse_message
1878
- header_part, body_part = raw_source.split(/#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m, 2)
1977
+ header_part, body_part = raw_source.lstrip.split(/#{CRLF}#{CRLF}|#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m, 2)
1879
1978
  self.header = header_part
1880
1979
  self.body = body_part
1881
1980
  end
1882
1981
 
1883
1982
  def raw_source=(value)
1884
- value.force_encoding("binary") if RUBY_VERSION >= "1.9.1"
1885
1983
  @raw_source = value.to_crlf
1984
+ @raw_source.force_encoding("binary") if RUBY_VERSION >= "1.9.1"
1985
+ @raw_source
1886
1986
  end
1887
1987
 
1888
1988
  # see comments to body=. We take data and process it lazily
@@ -1904,11 +2004,11 @@ module Mail
1904
2004
 
1905
2005
 
1906
2006
  def process_body_raw
1907
- @body = Mail::Body.new(@body_raw)
1908
- @body_raw = nil
1909
- separate_parts if @separate_parts
2007
+ @body = Mail::Body.new(@body_raw)
2008
+ @body_raw = nil
2009
+ separate_parts if @separate_parts
1910
2010
 
1911
- add_encoding_to_body
2011
+ add_encoding_to_body
1912
2012
  end
1913
2013
 
1914
2014
  def set_envelope_header
@@ -1938,16 +2038,19 @@ module Mail
1938
2038
  end
1939
2039
 
1940
2040
  def add_required_fields
1941
- add_multipart_mixed_header unless !body.multipart?
1942
- body = nil if body.nil?
1943
- add_message_id unless (has_message_id? || self.class == Mail::Part)
1944
- add_date unless has_date?
1945
- add_mime_version unless has_mime_version?
2041
+ add_required_message_fields
2042
+ add_multipart_mixed_header if body.multipart?
1946
2043
  add_content_type unless has_content_type?
1947
2044
  add_charset unless has_charset?
1948
2045
  add_content_transfer_encoding unless has_content_transfer_encoding?
1949
2046
  end
1950
2047
 
2048
+ def add_required_message_fields
2049
+ add_date unless has_date?
2050
+ add_mime_version unless has_mime_version?
2051
+ add_message_id unless has_message_id?
2052
+ end
2053
+
1951
2054
  def add_multipart_alternate_header
1952
2055
  header['content-type'] = ContentTypeField.with_boundary('multipart/alternative').value
1953
2056
  header['content_type'].parameters[:charset] = @charset
@@ -2032,7 +2135,7 @@ module Mail
2032
2135
  if perform_deliveries
2033
2136
  delivery_method.deliver!(self)
2034
2137
  end
2035
- rescue Exception => e # Net::SMTP errors or sendmail pipe errors
2138
+ rescue => e # Net::SMTP errors or sendmail pipe errors
2036
2139
  raise e if raise_delivery_errors
2037
2140
  end
2038
2141
  end
@@ -364,7 +364,7 @@ module Mail #:nodoc:
364
364
  # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
365
365
  # "日本語".mb_chars.titleize # => "日本語"
366
366
  def titleize
367
- chars(downcase.to_s.gsub(/\b('?[\S])/u) { Unicode.apply_mapping $1, :uppercase_mapping })
367
+ chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.apply_mapping $1, :uppercase_mapping })
368
368
  end
369
369
  alias_method :titlecase, :titleize
370
370
 
@@ -412,7 +412,7 @@ module Mail #:nodoc:
412
412
  chars(Unicode.tidy_bytes(@wrapped_string, force))
413
413
  end
414
414
 
415
- %w(capitalize downcase lstrip reverse rstrip slice strip tidy_bytes upcase).each do |method|
415
+ %w(capitalize downcase lstrip reverse rstrip slice strip tidy_bytes upcase).each do |method|
416
416
  # Only define a corresponding bang method for methods defined in the proxy; On 1.9 the proxy will
417
417
  # exclude lstrip!, rstrip! and strip! because they are already work as expected on multibyte strings.
418
418
  if public_method_defined?(method)
@@ -341,7 +341,7 @@ module Mail
341
341
  def load
342
342
  begin
343
343
  @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read }
344
- rescue Exception => e
344
+ rescue => e
345
345
  raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), Mail::Multibyte is unusable")
346
346
  end
347
347
 
@@ -391,8 +391,10 @@ module Mail
391
391
  end
392
392
  end
393
393
 
394
- module ActiveSupport
395
- unless const_defined?(:Multibyte)
396
- Multibyte = Mail::Multibyte
394
+ unless defined?(ActiveSupport)
395
+ module ActiveSupport
396
+ unless const_defined?(:Multibyte)
397
+ Multibyte = Mail::Multibyte
398
+ end
397
399
  end
398
400
  end
@@ -1,5 +1,3 @@
1
- require 'mail/check_delivery_params'
2
-
3
1
  module Mail
4
2
 
5
3
  # A delivery method implementation which sends via exim.
@@ -38,16 +36,13 @@ module Mail
38
36
  #
39
37
  # mail.deliver!
40
38
  class Exim < Sendmail
41
- include Mail::CheckDeliveryParams
42
-
43
39
  def initialize(values)
44
40
  self.settings = { :location => '/usr/sbin/exim',
45
41
  :arguments => '-i -t' }.merge(values)
46
42
  end
47
43
 
48
44
  def self.call(path, arguments, destinations, mail)
49
- check_params(mail)
50
- IO.popen("#{path} #{arguments}", "w+") do |io|
45
+ popen "#{path} #{arguments}" do |io|
51
46
  io.puts mail.encoded.to_lf
52
47
  io.flush
53
48
  end
@@ -28,7 +28,7 @@ module Mail
28
28
  attr_accessor :settings
29
29
 
30
30
  def deliver!(mail)
31
- check_params(mail)
31
+ check_delivery_params(mail)
32
32
 
33
33
  if ::File.respond_to?(:makedirs)
34
34
  ::File.makedirs settings[:location]
@@ -41,27 +41,49 @@ module Mail
41
41
 
42
42
  def initialize(values)
43
43
  self.settings = { :location => '/usr/sbin/sendmail',
44
- :arguments => '-i -t' }.merge(values)
44
+ :arguments => '-i' }.merge(values)
45
45
  end
46
46
 
47
47
  attr_accessor :settings
48
48
 
49
49
  def deliver!(mail)
50
- check_params(mail)
50
+ smtp_from, smtp_to, message = check_delivery_params(mail)
51
51
 
52
- envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
53
- return_path = "-f " + '"' + envelope_from.escape_for_shell + '"' if envelope_from
52
+ from = "-f #{self.class.shellquote(smtp_from)}"
53
+ to = smtp_to.map { |_to| self.class.shellquote(_to) }.join(' ')
54
54
 
55
- arguments = [settings[:arguments], return_path].compact.join(" ")
56
-
57
- self.class.call(settings[:location], arguments, mail.destinations.collect(&:escape_for_shell).join(" "), mail)
55
+ arguments = "#{settings[:arguments]} #{from} --"
56
+ self.class.call(settings[:location], arguments, to, message)
58
57
  end
59
58
 
60
- def self.call(path, arguments, destinations, mail)
61
- IO.popen("#{path} #{arguments} #{destinations}", "w+") do |io|
62
- io.puts mail.encoded.to_lf
59
+ def self.call(path, arguments, destinations, encoded_message)
60
+ popen "#{path} #{arguments} #{destinations}" do |io|
61
+ io.puts encoded_message.to_lf
63
62
  io.flush
64
63
  end
65
64
  end
65
+
66
+ if RUBY_VERSION < '1.9.0'
67
+ def self.popen(command, &block)
68
+ IO.popen "#{command} 2>&1", 'w+', &block
69
+ end
70
+ else
71
+ def self.popen(command, &block)
72
+ IO.popen command, 'w+', :err => :out, &block
73
+ end
74
+ end
75
+
76
+ # The following is an adaptation of ruby 1.9.2's shellwords.rb file,
77
+ # it is modified to include '+' in the allowed list to allow for
78
+ # sendmail to accept email addresses as the sender with a + in them.
79
+ def self.shellquote(address)
80
+ # Process as a single byte sequence because not all shell
81
+ # implementations are multibyte aware.
82
+ #
83
+ # A LF cannot be escaped with a backslash because a backslash + LF
84
+ # combo is regarded as line continuation and simply ignored. Strip it.
85
+ escaped = address.gsub(/([^A-Za-z0-9_\s\+\-.,:\/@])/n, "\\\\\\1").gsub("\n", '')
86
+ %("#{escaped}")
87
+ end
66
88
  end
67
89
  end
@@ -89,57 +89,54 @@ module Mail
89
89
  :tls => nil
90
90
  }.merge!(values)
91
91
  end
92
-
92
+
93
93
  attr_accessor :settings
94
-
94
+
95
95
  # Send the message via SMTP.
96
96
  # The from and to attributes are optional. If not set, they are retrieve from the Message.
97
97
  def deliver!(mail)
98
- envelope_from, destinations, message = check_params(mail)
98
+ smtp_from, smtp_to, message = check_delivery_params(mail)
99
99
 
100
100
  smtp = Net::SMTP.new(settings[:address], settings[:port])
101
101
  if settings[:tls] || settings[:ssl]
102
102
  if smtp.respond_to?(:enable_tls)
103
- unless settings[:openssl_verify_mode]
104
- smtp.enable_tls
105
- else
106
- openssl_verify_mode = settings[:openssl_verify_mode]
107
- if openssl_verify_mode.kind_of?(String)
108
- openssl_verify_mode = "OpenSSL::SSL::VERIFY_#{openssl_verify_mode.upcase}".constantize
109
- end
110
- context = Net::SMTP.default_ssl_context
111
- context.verify_mode = openssl_verify_mode
112
- smtp.enable_tls(context)
113
- end
103
+ smtp.enable_tls(ssl_context)
114
104
  end
115
105
  elsif settings[:enable_starttls_auto]
116
- if smtp.respond_to?(:enable_starttls_auto)
117
- unless settings[:openssl_verify_mode]
118
- smtp.enable_starttls_auto
119
- else
120
- openssl_verify_mode = settings[:openssl_verify_mode]
121
- if openssl_verify_mode.kind_of?(String)
122
- openssl_verify_mode = "OpenSSL::SSL::VERIFY_#{openssl_verify_mode.upcase}".constantize
123
- end
124
- if RUBY_VERSION >= '1.9.0'
125
- context = Net::SMTP.default_ssl_context
126
- context.verify_mode = openssl_verify_mode
127
- smtp.enable_tls(context)
128
- else
129
- smtp.enable_tls(openssl_verify_mode)
130
- end
131
- end
106
+ if smtp.respond_to?(:enable_starttls_auto)
107
+ smtp.enable_starttls_auto(ssl_context)
132
108
  end
133
109
  end
134
-
110
+
135
111
  response = nil
136
112
  smtp.start(settings[:domain], settings[:user_name], settings[:password], settings[:authentication]) do |smtp_obj|
137
- response = smtp_obj.sendmail(message, envelope_from, destinations)
113
+ response = smtp_obj.sendmail(message, smtp_from, smtp_to)
138
114
  end
139
115
 
140
- return settings[:return_response] ? response : self
116
+ if settings[:return_response]
117
+ response
118
+ else
119
+ self
120
+ end
141
121
  end
142
122
 
143
-
123
+
124
+ private
125
+
126
+ # Allow SSL context to be configured via settings, for Ruby >= 1.9
127
+ # Just returns openssl verify mode for Ruby 1.8.x
128
+ def ssl_context
129
+ openssl_verify_mode = settings[:openssl_verify_mode]
130
+
131
+ if openssl_verify_mode.kind_of?(String)
132
+ openssl_verify_mode = "OpenSSL::SSL::VERIFY_#{openssl_verify_mode.upcase}".constantize
133
+ end
134
+
135
+ context = Net::SMTP.default_ssl_context
136
+ context.verify_mode = openssl_verify_mode
137
+ context.ca_path = settings[:ca_path] if settings[:ca_path]
138
+ context.ca_file = settings[:ca_file] if settings[:ca_file]
139
+ context
140
+ end
144
141
  end
145
142
  end
@@ -44,18 +44,18 @@ module Mail
44
44
  self.smtp = values[:connection]
45
45
  self.settings = values
46
46
  end
47
-
47
+
48
48
  attr_accessor :smtp
49
49
  attr_accessor :settings
50
-
50
+
51
51
  # Send the message via SMTP.
52
52
  # The from and to attributes are optional. If not set, they are retrieve from the Message.
53
53
  def deliver!(mail)
54
- envelope_from, destinations, message = check_params(mail)
55
- response = smtp.sendmail(message, envelope_from, destinations)
54
+ smtp_from, smtp_to, message = check_delivery_params(mail)
55
+ response = smtp.sendmail(message, smtp_from, smtp_to)
56
56
 
57
- settings[:return_response] ? response : self
57
+ settings[:return_response] ? response : self
58
58
  end
59
-
59
+
60
60
  end
61
61
  end
@@ -30,13 +30,13 @@ module Mail
30
30
  end
31
31
 
32
32
  def initialize(values)
33
- @settings = {}
33
+ @settings = values.dup
34
34
  end
35
35
 
36
36
  attr_accessor :settings
37
37
 
38
38
  def deliver!(mail)
39
- check_params(mail)
39
+ check_delivery_params(mail)
40
40
  Mail::TestMailer.deliveries << mail
41
41
  end
42
42