mail 2.4.4 → 2.6.3

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 (163) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +156 -0
  3. data/CONTRIBUTING.md +21 -6
  4. data/Dependencies.txt +0 -1
  5. data/Gemfile +10 -21
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +52 -32
  8. data/Rakefile +11 -22
  9. data/lib/mail/attachments_list.rb +2 -2
  10. data/lib/mail/body.rb +33 -9
  11. data/lib/mail/check_delivery_params.rb +20 -0
  12. data/lib/mail/configuration.rb +1 -1
  13. data/lib/mail/{patterns.rb → constants.rb} +26 -6
  14. data/lib/mail/core_extensions/nil.rb +4 -2
  15. data/lib/mail/core_extensions/object.rb +8 -8
  16. data/lib/mail/core_extensions/smtp.rb +12 -13
  17. data/lib/mail/core_extensions/string.rb +19 -6
  18. data/lib/mail/elements/address.rb +82 -141
  19. data/lib/mail/elements/address_list.rb +9 -42
  20. data/lib/mail/elements/content_disposition_element.rb +3 -7
  21. data/lib/mail/elements/content_location_element.rb +2 -6
  22. data/lib/mail/elements/content_transfer_encoding_element.rb +3 -10
  23. data/lib/mail/elements/content_type_element.rb +4 -8
  24. data/lib/mail/elements/date_time_element.rb +3 -7
  25. data/lib/mail/elements/envelope_from_element.rb +18 -13
  26. data/lib/mail/elements/message_ids_element.rb +1 -6
  27. data/lib/mail/elements/mime_version_element.rb +3 -7
  28. data/lib/mail/elements/phrase_list.rb +2 -7
  29. data/lib/mail/elements/received_element.rb +3 -7
  30. data/lib/mail/elements.rb +12 -12
  31. data/lib/mail/encodings/quoted_printable.rb +4 -3
  32. data/lib/mail/encodings/unix_to_unix.rb +17 -0
  33. data/lib/mail/encodings.rb +53 -38
  34. data/lib/mail/envelope.rb +0 -5
  35. data/lib/mail/field.rb +131 -118
  36. data/lib/mail/field_list.rb +18 -18
  37. data/lib/mail/fields/bcc_field.rb +2 -2
  38. data/lib/mail/fields/cc_field.rb +2 -2
  39. data/lib/mail/fields/comments_field.rb +1 -1
  40. data/lib/mail/fields/common/common_address.rb +32 -22
  41. data/lib/mail/fields/common/common_date.rb +0 -7
  42. data/lib/mail/fields/common/common_field.rb +10 -3
  43. data/lib/mail/fields/common/common_message_id.rb +9 -5
  44. data/lib/mail/fields/common/parameter_hash.rb +3 -3
  45. data/lib/mail/fields/content_disposition_field.rb +1 -0
  46. data/lib/mail/fields/content_id_field.rb +1 -2
  47. data/lib/mail/fields/content_transfer_encoding_field.rb +2 -8
  48. data/lib/mail/fields/content_type_field.rb +5 -2
  49. data/lib/mail/fields/date_field.rb +14 -14
  50. data/lib/mail/fields/from_field.rb +2 -2
  51. data/lib/mail/fields/in_reply_to_field.rb +2 -1
  52. data/lib/mail/fields/keywords_field.rb +1 -1
  53. data/lib/mail/fields/message_id_field.rb +2 -3
  54. data/lib/mail/fields/references_field.rb +2 -1
  55. data/lib/mail/fields/reply_to_field.rb +2 -2
  56. data/lib/mail/fields/resent_bcc_field.rb +2 -2
  57. data/lib/mail/fields/resent_cc_field.rb +2 -2
  58. data/lib/mail/fields/resent_from_field.rb +2 -2
  59. data/lib/mail/fields/resent_sender_field.rb +3 -3
  60. data/lib/mail/fields/resent_to_field.rb +2 -2
  61. data/lib/mail/fields/sender_field.rb +8 -8
  62. data/lib/mail/fields/to_field.rb +2 -2
  63. data/lib/mail/fields/unstructured_field.rb +44 -31
  64. data/lib/mail/fields.rb +32 -32
  65. data/lib/mail/header.rb +47 -37
  66. data/lib/mail/mail.rb +15 -1
  67. data/lib/mail/matchers/has_sent_mail.rb +36 -3
  68. data/lib/mail/message.rb +164 -57
  69. data/lib/mail/multibyte/chars.rb +4 -4
  70. data/lib/mail/multibyte/unicode.rb +9 -1
  71. data/lib/mail/network/delivery_methods/exim.rb +2 -3
  72. data/lib/mail/network/delivery_methods/file_delivery.rb +5 -0
  73. data/lib/mail/network/delivery_methods/sendmail.rb +35 -8
  74. data/lib/mail/network/delivery_methods/smtp.rb +34 -45
  75. data/lib/mail/network/delivery_methods/smtp_connection.rb +9 -22
  76. data/lib/mail/network/delivery_methods/test_mailer.rb +6 -2
  77. data/lib/mail/network/retriever_methods/imap.rb +32 -19
  78. data/lib/mail/network/retriever_methods/pop3.rb +2 -2
  79. data/lib/mail/network/retriever_methods/test_retriever.rb +11 -15
  80. data/lib/mail/network.rb +9 -9
  81. data/lib/mail/parsers/address_lists_parser.rb +132 -0
  82. data/lib/mail/parsers/content_disposition_parser.rb +67 -0
  83. data/lib/mail/parsers/content_location_parser.rb +35 -0
  84. data/lib/mail/parsers/content_transfer_encoding_parser.rb +33 -0
  85. data/lib/mail/parsers/content_type_parser.rb +66 -0
  86. data/lib/mail/parsers/date_time_parser.rb +36 -0
  87. data/lib/mail/parsers/envelope_from_parser.rb +45 -0
  88. data/lib/mail/parsers/message_ids_parser.rb +39 -0
  89. data/lib/mail/parsers/mime_version_parser.rb +41 -0
  90. data/lib/mail/parsers/phrase_lists_parser.rb +33 -0
  91. data/lib/mail/parsers/ragel/common.rl +185 -0
  92. data/lib/mail/parsers/ragel/date_time.rl +30 -0
  93. data/lib/mail/parsers/ragel/parser_info.rb +61 -0
  94. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +14864 -0
  95. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +37 -0
  96. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +751 -0
  97. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +37 -0
  98. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +614 -0
  99. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +37 -0
  100. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +447 -0
  101. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +37 -0
  102. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +825 -0
  103. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +37 -0
  104. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +817 -0
  105. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +37 -0
  106. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +2149 -0
  107. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +37 -0
  108. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +1570 -0
  109. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +37 -0
  110. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +440 -0
  111. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +37 -0
  112. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +564 -0
  113. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +37 -0
  114. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +51 -0
  115. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +5144 -0
  116. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +37 -0
  117. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +37 -0
  118. data/lib/mail/parsers/ragel/ruby.rb +40 -0
  119. data/lib/mail/parsers/ragel.rb +17 -0
  120. data/lib/mail/parsers/received_parser.rb +47 -0
  121. data/lib/mail/parsers.rb +26 -0
  122. data/lib/mail/part.rb +6 -2
  123. data/lib/mail/parts_list.rb +5 -3
  124. data/lib/mail/utilities.rb +70 -60
  125. data/lib/mail/values/unicode_tables.dat +0 -0
  126. data/lib/mail/version.rb +7 -15
  127. data/lib/mail/version_specific/ruby_1_8.rb +25 -4
  128. data/lib/mail/version_specific/ruby_1_9.rb +110 -30
  129. data/lib/mail.rb +20 -19
  130. metadata +129 -73
  131. data/Gemfile.lock +0 -36
  132. data/lib/VERSION +0 -4
  133. data/lib/mail/core_extensions/shell_escape.rb +0 -56
  134. data/lib/mail/parsers/address_lists.rb +0 -64
  135. data/lib/mail/parsers/address_lists.treetop +0 -19
  136. data/lib/mail/parsers/content_disposition.rb +0 -535
  137. data/lib/mail/parsers/content_disposition.treetop +0 -46
  138. data/lib/mail/parsers/content_location.rb +0 -139
  139. data/lib/mail/parsers/content_location.treetop +0 -20
  140. data/lib/mail/parsers/content_transfer_encoding.rb +0 -162
  141. data/lib/mail/parsers/content_transfer_encoding.treetop +0 -20
  142. data/lib/mail/parsers/content_type.rb +0 -967
  143. data/lib/mail/parsers/content_type.treetop +0 -68
  144. data/lib/mail/parsers/date_time.rb +0 -114
  145. data/lib/mail/parsers/date_time.treetop +0 -11
  146. data/lib/mail/parsers/envelope_from.rb +0 -194
  147. data/lib/mail/parsers/envelope_from.treetop +0 -32
  148. data/lib/mail/parsers/message_ids.rb +0 -45
  149. data/lib/mail/parsers/message_ids.treetop +0 -15
  150. data/lib/mail/parsers/mime_version.rb +0 -144
  151. data/lib/mail/parsers/mime_version.treetop +0 -19
  152. data/lib/mail/parsers/phrase_lists.rb +0 -45
  153. data/lib/mail/parsers/phrase_lists.treetop +0 -15
  154. data/lib/mail/parsers/received.rb +0 -71
  155. data/lib/mail/parsers/received.treetop +0 -11
  156. data/lib/mail/parsers/rfc2045.rb +0 -464
  157. data/lib/mail/parsers/rfc2045.treetop +0 -36
  158. data/lib/mail/parsers/rfc2822.rb +0 -5341
  159. data/lib/mail/parsers/rfc2822.treetop +0 -410
  160. data/lib/mail/parsers/rfc2822_obsolete.rb +0 -3768
  161. data/lib/mail/parsers/rfc2822_obsolete.treetop +0 -241
  162. data/lib/tasks/corpus.rake +0 -125
  163. data/lib/tasks/treetop.rake +0 -10
data/lib/mail/message.rb CHANGED
@@ -46,7 +46,7 @@ module Mail
46
46
  # (i.e., a line with nothing preceding the CRLF).
47
47
  class Message
48
48
 
49
- include Patterns
49
+ include Constants
50
50
  include Utilities
51
51
 
52
52
  # ==Making an email
@@ -105,9 +105,12 @@ module Mail
105
105
  @html_part = nil
106
106
  @errors = nil
107
107
  @header = nil
108
- @charset = 'UTF-8'
108
+ @charset = self.class.default_charset
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
 
@@ -204,6 +207,10 @@ module Mail
204
207
  # define a delivery_handler
205
208
  attr_accessor :raise_delivery_errors
206
209
 
210
+ def self.default_charset; @@default_charset; end
211
+ def self.default_charset=(charset); @@default_charset = charset; end
212
+ self.default_charset = 'UTF-8'
213
+
207
214
  def register_for_delivery_notification(observer)
208
215
  STDERR.puts("Message#register_for_delivery_notification is deprecated, please call Mail.register_observer instead")
209
216
  Mail.register_observer(observer)
@@ -237,7 +244,7 @@ module Mail
237
244
  # This method bypasses checking perform_deliveries and raise_delivery_errors,
238
245
  # so use with caution.
239
246
  #
240
- # It still however fires off the intercepters and calls the observers callbacks if they are defined.
247
+ # It still however fires off the interceptors and calls the observers callbacks if they are defined.
241
248
  #
242
249
  # Returns self
243
250
  def deliver!
@@ -350,17 +357,23 @@ module Mail
350
357
  return false unless other.respond_to?(:encoded)
351
358
 
352
359
  if self.message_id && other.message_id
353
- result = (self.encoded == other.encoded)
360
+ self.encoded == other.encoded
354
361
  else
355
362
  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
363
+ begin
364
+ self.message_id, other.message_id = '<temp@test>', '<temp@test>'
365
+ self.encoded == other.encoded
366
+ ensure
367
+ self.message_id, other.message_id = self_message_id, other_message_id
368
+ end
361
369
  end
362
370
  end
363
371
 
372
+ def initialize_copy(original)
373
+ super
374
+ @header = @header.dup
375
+ end
376
+
364
377
  # Provides access to the raw source of the message as it was when it
365
378
  # was instantiated. This is set at initialization and so is untouched
366
379
  # by the parsers or decoder / encoders
@@ -938,8 +951,8 @@ module Mail
938
951
  #
939
952
  # Example:
940
953
  #
941
- # mail.sender = 'Mikel <mikel@test.lindsaar.net>'
942
- # mail.sender #=> 'mikel@test.lindsaar.net'
954
+ # mail.resent_sender = 'Mikel <mikel@test.lindsaar.net>'
955
+ # mail.resent_sender #=> 'mikel@test.lindsaar.net'
943
956
  def resent_sender=( val )
944
957
  header[:resent_sender] = val
945
958
  end
@@ -1023,6 +1036,82 @@ module Mail
1023
1036
  header[:sender] = val
1024
1037
  end
1025
1038
 
1039
+ # Returns the SMTP Envelope From value of the mail object, as a single
1040
+ # string of an address spec.
1041
+ #
1042
+ # Defaults to Return-Path, Sender, or the first From address.
1043
+ #
1044
+ # Example:
1045
+ #
1046
+ # mail.smtp_envelope_from = 'Mikel <mikel@test.lindsaar.net>'
1047
+ # mail.smtp_envelope_from #=> 'mikel@test.lindsaar.net'
1048
+ #
1049
+ # Also allows you to set the value by passing a value as a parameter
1050
+ #
1051
+ # Example:
1052
+ #
1053
+ # mail.smtp_envelope_from 'Mikel <mikel@test.lindsaar.net>'
1054
+ # mail.smtp_envelope_from #=> 'mikel@test.lindsaar.net'
1055
+ def smtp_envelope_from( val = nil )
1056
+ if val
1057
+ self.smtp_envelope_from = val
1058
+ else
1059
+ @smtp_envelope_from || return_path || sender || from_addrs.first
1060
+ end
1061
+ end
1062
+
1063
+ # Sets the From address on the SMTP Envelope.
1064
+ #
1065
+ # Example:
1066
+ #
1067
+ # mail.smtp_envelope_from = 'Mikel <mikel@test.lindsaar.net>'
1068
+ # mail.smtp_envelope_from #=> 'mikel@test.lindsaar.net'
1069
+ def smtp_envelope_from=( val )
1070
+ @smtp_envelope_from = val
1071
+ end
1072
+
1073
+ # Returns the SMTP Envelope To value of the mail object.
1074
+ #
1075
+ # Defaults to #destinations: To, Cc, and Bcc addresses.
1076
+ #
1077
+ # Example:
1078
+ #
1079
+ # mail.smtp_envelope_to = 'Mikel <mikel@test.lindsaar.net>'
1080
+ # mail.smtp_envelope_to #=> 'mikel@test.lindsaar.net'
1081
+ #
1082
+ # Also allows you to set the value by passing a value as a parameter
1083
+ #
1084
+ # Example:
1085
+ #
1086
+ # mail.smtp_envelope_to ['Mikel <mikel@test.lindsaar.net>', 'Lindsaar <lindsaar@test.lindsaar.net>']
1087
+ # mail.smtp_envelope_to #=> ['mikel@test.lindsaar.net', 'lindsaar@test.lindsaar.net']
1088
+ def smtp_envelope_to( val = nil )
1089
+ if val
1090
+ self.smtp_envelope_to = val
1091
+ else
1092
+ @smtp_envelope_to || destinations
1093
+ end
1094
+ end
1095
+
1096
+ # Sets the To addresses on the SMTP Envelope.
1097
+ #
1098
+ # Example:
1099
+ #
1100
+ # mail.smtp_envelope_to = 'Mikel <mikel@test.lindsaar.net>'
1101
+ # mail.smtp_envelope_to #=> 'mikel@test.lindsaar.net'
1102
+ #
1103
+ # mail.smtp_envelope_to = ['Mikel <mikel@test.lindsaar.net>', 'Lindsaar <lindsaar@test.lindsaar.net>']
1104
+ # mail.smtp_envelope_to #=> ['mikel@test.lindsaar.net', 'lindsaar@test.lindsaar.net']
1105
+ def smtp_envelope_to=( val )
1106
+ @smtp_envelope_to =
1107
+ case val
1108
+ when Array, NilClass
1109
+ val
1110
+ else
1111
+ [val]
1112
+ end
1113
+ end
1114
+
1026
1115
  # Returns the decoded value of the subject field, as a single string.
1027
1116
  #
1028
1117
  # Example:
@@ -1309,7 +1398,7 @@ module Mail
1309
1398
  header.has_date?
1310
1399
  end
1311
1400
 
1312
- # Returns true if the message has a Date field, the field may or may
1401
+ # Returns true if the message has a Mime-Version field, the field may or may
1313
1402
  # not have a value, but the field exists or not.
1314
1403
  def has_mime_version?
1315
1404
  header.has_mime_version?
@@ -1411,7 +1500,7 @@ module Mail
1411
1500
 
1412
1501
  # Returns the MIME media type of part we are on, this is taken from the content-type header
1413
1502
  def mime_type
1414
- content_type ? header[:content_type].string : nil rescue nil
1503
+ has_content_type? ? header[:content_type].string : nil rescue nil
1415
1504
  end
1416
1505
 
1417
1506
  def message_content_type
@@ -1422,7 +1511,7 @@ module Mail
1422
1511
  # Returns the character set defined in the content type field
1423
1512
  def charset
1424
1513
  if @header
1425
- content_type ? content_type_parameters['charset'] : @charset
1514
+ has_content_type? ? content_type_parameters['charset'] : @charset
1426
1515
  else
1427
1516
  @charset
1428
1517
  end
@@ -1515,8 +1604,8 @@ module Mail
1515
1604
  end
1516
1605
 
1517
1606
  # 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.
1607
+ # the receiver object (either the entire email or a part within) and all
1608
+ # of its descendants.
1520
1609
  #
1521
1610
  # It also allows you to add attachments to the mail object directly, like so:
1522
1611
  #
@@ -1559,9 +1648,7 @@ module Mail
1559
1648
  # Accessor for html_part
1560
1649
  def html_part(&block)
1561
1650
  if block_given?
1562
- @html_part = Mail::Part.new(&block)
1563
- add_multipart_alternate_header unless html_part.blank?
1564
- add_part(@html_part)
1651
+ self.html_part = Mail::Part.new(:content_type => 'text/html', &block)
1565
1652
  else
1566
1653
  @html_part || find_first_mime_type('text/html')
1567
1654
  end
@@ -1570,9 +1657,7 @@ module Mail
1570
1657
  # Accessor for text_part
1571
1658
  def text_part(&block)
1572
1659
  if block_given?
1573
- @text_part = Mail::Part.new(&block)
1574
- add_multipart_alternate_header unless html_part.blank?
1575
- add_part(@text_part)
1660
+ self.text_part = Mail::Part.new(:content_type => 'text/plain', &block)
1576
1661
  else
1577
1662
  @text_part || find_first_mime_type('text/plain')
1578
1663
  end
@@ -1581,36 +1666,54 @@ module Mail
1581
1666
  # Helper to add a html part to a multipart/alternative email. If this and
1582
1667
  # text_part are both defined in a message, then it will be a multipart/alternative
1583
1668
  # message and set itself that way.
1584
- def html_part=(msg = nil)
1669
+ def html_part=(msg)
1670
+ # Assign the html part and set multipart/alternative if there's a text part.
1585
1671
  if msg
1586
1672
  @html_part = msg
1587
- else
1588
- @html_part = Mail::Part.new('Content-Type: text/html;')
1673
+ @html_part.content_type = 'text/html' unless @html_part.has_content_type?
1674
+ add_multipart_alternate_header if text_part
1675
+ add_part @html_part
1676
+
1677
+ # If nil, delete the html part and back out of multipart/alternative.
1678
+ elsif @html_part
1679
+ parts.delete_if { |p| p.object_id == @html_part.object_id }
1680
+ @html_part = nil
1681
+ if text_part
1682
+ self.content_type = nil
1683
+ body.boundary = nil
1684
+ end
1589
1685
  end
1590
- add_multipart_alternate_header unless text_part.blank?
1591
- add_part(@html_part)
1592
1686
  end
1593
1687
 
1594
1688
  # Helper to add a text part to a multipart/alternative email. If this and
1595
1689
  # html_part are both defined in a message, then it will be a multipart/alternative
1596
1690
  # message and set itself that way.
1597
- def text_part=(msg = nil)
1691
+ def text_part=(msg)
1692
+ # Assign the text part and set multipart/alternative if there's an html part.
1598
1693
  if msg
1599
1694
  @text_part = msg
1600
- else
1601
- @text_part = Mail::Part.new('Content-Type: text/plain;')
1695
+ @text_part.content_type = 'text/plain' unless @text_part.has_content_type?
1696
+ add_multipart_alternate_header if html_part
1697
+ add_part @text_part
1698
+
1699
+ # If nil, delete the text part and back out of multipart/alternative.
1700
+ elsif @text_part
1701
+ parts.delete_if { |p| p.object_id == @text_part.object_id }
1702
+ @text_part = nil
1703
+ if html_part
1704
+ self.content_type = nil
1705
+ body.boundary = nil
1706
+ end
1602
1707
  end
1603
- add_multipart_alternate_header unless html_part.blank?
1604
- add_part(@text_part)
1605
1708
  end
1606
1709
 
1607
1710
  # Adds a part to the parts list or creates the part list
1608
1711
  def add_part(part)
1609
1712
  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
1713
+ @text_part = Mail::Part.new('Content-Type: text/plain;')
1714
+ @text_part.body = body.decoded
1715
+ self.body << @text_part
1716
+ add_multipart_alternate_header
1614
1717
  end
1615
1718
  add_boundary
1616
1719
  self.body << part
@@ -1646,7 +1749,7 @@ module Mail
1646
1749
  # m.add_file(:filename => 'filename.png', :content => File.read('/path/to/file.jpg'))
1647
1750
  #
1648
1751
  # 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
1752
+ # to a MIME multipart email, moving whatever plain text body you had into its own text
1650
1753
  # plain part.
1651
1754
  #
1652
1755
  # Example:
@@ -1683,7 +1786,7 @@ module Mail
1683
1786
  self.body << text_part
1684
1787
  end
1685
1788
 
1686
- # Encodes the message, calls encode on all it's parts, gets an email message
1789
+ # Encodes the message, calls encode on all its parts, gets an email message
1687
1790
  # ready to send
1688
1791
  def ready_to_send!
1689
1792
  identify_and_set_transfer_encoding
@@ -1864,6 +1967,8 @@ module Mail
1864
1967
 
1865
1968
  private
1866
1969
 
1970
+ HEADER_SEPARATOR = /#{CRLF}#{CRLF}|#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m
1971
+
1867
1972
  # 2.1. General Description
1868
1973
  # A message consists of header fields (collectively called "the header
1869
1974
  # of the message") followed, optionally, by a body. The header is a
@@ -1875,15 +1980,13 @@ module Mail
1875
1980
  # Additionally, I allow for the case where someone might have put whitespace
1876
1981
  # on the "gap line"
1877
1982
  def parse_message
1878
- header_part, body_part = raw_source.split(/#{CRLF}#{WSP}*#{CRLF}/m, 2)
1879
- # index = raw_source.index(/#{CRLF}#{WSP}*#{CRLF}/m, 2)
1880
- # self.header = (index) ? header_part[0,index] : nil
1881
- # lazy_body ( [raw_source, index+1])
1983
+ header_part, body_part = raw_source.lstrip.split(HEADER_SEPARATOR, 2)
1882
1984
  self.header = header_part
1883
1985
  self.body = body_part
1884
1986
  end
1885
1987
 
1886
1988
  def raw_source=(value)
1989
+ value = value.dup.force_encoding(Encoding::BINARY) if RUBY_VERSION >= "1.9.1"
1887
1990
  @raw_source = value.to_crlf
1888
1991
  end
1889
1992
 
@@ -1906,17 +2009,18 @@ module Mail
1906
2009
 
1907
2010
 
1908
2011
  def process_body_raw
1909
- @body = Mail::Body.new(@body_raw)
1910
- @body_raw = nil
1911
- separate_parts if @separate_parts
2012
+ @body = Mail::Body.new(@body_raw)
2013
+ @body_raw = nil
2014
+ separate_parts if @separate_parts
1912
2015
 
1913
- add_encoding_to_body
2016
+ add_encoding_to_body
1914
2017
  end
1915
2018
 
1916
2019
  def set_envelope_header
1917
- if match_data = raw_source.to_s.match(/\AFrom\s(#{TEXT}+)#{CRLF}(.*)/m)
2020
+ raw_string = raw_source.to_s
2021
+ if match_data = raw_source.to_s.match(/\AFrom\s(#{TEXT}+)#{CRLF}/m)
1918
2022
  set_envelope(match_data[1])
1919
- self.raw_source = match_data[2]
2023
+ self.raw_source = raw_string.sub(match_data[0], "")
1920
2024
  end
1921
2025
  end
1922
2026
 
@@ -1939,16 +2043,19 @@ module Mail
1939
2043
  end
1940
2044
 
1941
2045
  def add_required_fields
1942
- add_multipart_mixed_header unless !body.multipart?
1943
- body = nil if body.nil?
1944
- add_message_id unless (has_message_id? || self.class == Mail::Part)
1945
- add_date unless has_date?
1946
- add_mime_version unless has_mime_version?
2046
+ add_required_message_fields
2047
+ add_multipart_mixed_header if body.multipart?
1947
2048
  add_content_type unless has_content_type?
1948
2049
  add_charset unless has_charset?
1949
2050
  add_content_transfer_encoding unless has_content_transfer_encoding?
1950
2051
  end
1951
2052
 
2053
+ def add_required_message_fields
2054
+ add_date unless has_date?
2055
+ add_mime_version unless has_mime_version?
2056
+ add_message_id unless has_message_id?
2057
+ end
2058
+
1952
2059
  def add_multipart_alternate_header
1953
2060
  header['content-type'] = ContentTypeField.with_boundary('multipart/alternative').value
1954
2061
  header['content_type'].parameters[:charset] = @charset
@@ -2033,7 +2140,7 @@ module Mail
2033
2140
  if perform_deliveries
2034
2141
  delivery_method.deliver!(self)
2035
2142
  end
2036
- rescue Exception => e # Net::SMTP errors or sendmail pipe errors
2143
+ rescue => e # Net::SMTP errors or sendmail pipe errors
2037
2144
  raise e if raise_delivery_errors
2038
2145
  end
2039
2146
  end
@@ -2047,7 +2154,7 @@ module Mail
2047
2154
  else
2048
2155
  if encoding = Encoding.find(charset) rescue nil
2049
2156
  body_text.force_encoding(encoding)
2050
- return body_text.encode(Encoding::UTF_8)
2157
+ return body_text.encode(Encoding::UTF_8, :undef => :replace, :invalid => :replace, :replace => '')
2051
2158
  end
2052
2159
  end
2053
2160
  end
@@ -339,7 +339,7 @@ module Mail #:nodoc:
339
339
  # Example:
340
340
  # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
341
341
  def upcase
342
- chars(Unicode.apply_mapping(@wrapped_string), :uppercase_mapping)
342
+ chars(Unicode.apply_mapping(@wrapped_string, :uppercase_mapping))
343
343
  end
344
344
 
345
345
  # Convert characters in the string to lowercase.
@@ -347,7 +347,7 @@ module Mail #:nodoc:
347
347
  # Example:
348
348
  # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
349
349
  def downcase
350
- chars(Unicode.apply_mapping(@wrapped_string), :lowercase_mapping)
350
+ chars(Unicode.apply_mapping(@wrapped_string, :lowercase_mapping))
351
351
  end
352
352
 
353
353
  # Converts the first character to uppercase and the remainder to lowercase.
@@ -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
 
@@ -390,3 +390,11 @@ module Mail
390
390
  end
391
391
  end
392
392
  end
393
+
394
+ unless defined?(ActiveSupport)
395
+ module ActiveSupport
396
+ unless const_defined?(:Multibyte)
397
+ Multibyte = Mail::Multibyte
398
+ end
399
+ end
400
+ end
@@ -36,14 +36,13 @@ module Mail
36
36
  #
37
37
  # mail.deliver!
38
38
  class Exim < Sendmail
39
-
40
39
  def initialize(values)
41
40
  self.settings = { :location => '/usr/sbin/exim',
42
41
  :arguments => '-i -t' }.merge(values)
43
42
  end
44
43
 
45
- def self.call(path, arguments, mail)
46
- IO.popen("#{path} #{arguments}", "w+") do |io|
44
+ def self.call(path, arguments, destinations, mail)
45
+ popen "#{path} #{arguments}" do |io|
47
46
  io.puts mail.encoded.to_lf
48
47
  io.flush
49
48
  end
@@ -1,3 +1,5 @@
1
+ require 'mail/check_delivery_params'
2
+
1
3
  module Mail
2
4
 
3
5
  # FileDelivery class delivers emails into multiple files based on the destination
@@ -11,6 +13,7 @@ module Mail
11
13
  # Make sure the path you specify with :location is writable by the Ruby process
12
14
  # running Mail.
13
15
  class FileDelivery
16
+ include Mail::CheckDeliveryParams
14
17
 
15
18
  if RUBY_VERSION >= '1.9.1'
16
19
  require 'fileutils'
@@ -25,6 +28,8 @@ module Mail
25
28
  attr_accessor :settings
26
29
 
27
30
  def deliver!(mail)
31
+ check_delivery_params(mail)
32
+
28
33
  if ::File.respond_to?(:makedirs)
29
34
  ::File.makedirs settings[:location]
30
35
  else
@@ -1,3 +1,5 @@
1
+ require 'mail/check_delivery_params'
2
+
1
3
  module Mail
2
4
  # A delivery method implementation which sends via sendmail.
3
5
  #
@@ -35,28 +37,53 @@ module Mail
35
37
  #
36
38
  # mail.deliver!
37
39
  class Sendmail
40
+ include Mail::CheckDeliveryParams
38
41
 
39
42
  def initialize(values)
40
43
  self.settings = { :location => '/usr/sbin/sendmail',
41
- :arguments => '-i -t' }.merge(values)
44
+ :arguments => '-i' }.merge(values)
42
45
  end
43
46
 
44
47
  attr_accessor :settings
45
48
 
46
49
  def deliver!(mail)
47
- envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
48
- return_path = "-f " + '"' + envelope_from.escape_for_shell + '"' if envelope_from
50
+ smtp_from, smtp_to, message = check_delivery_params(mail)
49
51
 
50
- arguments = [settings[:arguments], return_path].compact.join(" ")
52
+ from = "-f #{self.class.shellquote(smtp_from)}"
53
+ to = smtp_to.map { |_to| self.class.shellquote(_to) }.join(' ')
51
54
 
52
- 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)
53
57
  end
54
58
 
55
- def self.call(path, arguments, destinations, mail)
56
- IO.popen("#{path} #{arguments} #{destinations}", "w+") do |io|
57
- 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
58
62
  io.flush
59
63
  end
60
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
61
88
  end
62
89
  end