mail 2.5.2 → 2.6.4

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 (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +124 -0
  3. data/CONTRIBUTING.md +21 -6
  4. data/Dependencies.txt +0 -1
  5. data/Gemfile +7 -22
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +86 -31
  8. data/Rakefile +11 -22
  9. data/lib/mail/attachments_list.rb +4 -2
  10. data/lib/mail/body.rb +36 -11
  11. data/lib/mail/check_delivery_params.rb +13 -22
  12. data/lib/mail/configuration.rb +1 -0
  13. data/lib/mail/{patterns.rb → constants.rb} +29 -6
  14. data/lib/mail/core_extensions/smtp.rb +13 -13
  15. data/lib/mail/core_extensions/string/access.rb +1 -0
  16. data/lib/mail/core_extensions/string/multibyte.rb +1 -0
  17. data/lib/mail/core_extensions/string.rb +5 -17
  18. data/lib/mail/elements/address.rb +86 -145
  19. data/lib/mail/elements/address_list.rb +10 -42
  20. data/lib/mail/elements/content_disposition_element.rb +4 -7
  21. data/lib/mail/elements/content_location_element.rb +8 -11
  22. data/lib/mail/elements/content_transfer_encoding_element.rb +4 -10
  23. data/lib/mail/elements/content_type_element.rb +5 -8
  24. data/lib/mail/elements/date_time_element.rb +4 -7
  25. data/lib/mail/elements/envelope_from_element.rb +19 -13
  26. data/lib/mail/elements/message_ids_element.rb +8 -11
  27. data/lib/mail/elements/mime_version_element.rb +4 -7
  28. data/lib/mail/elements/phrase_list.rb +3 -7
  29. data/lib/mail/elements/received_element.rb +4 -7
  30. data/lib/mail/elements.rb +1 -0
  31. data/lib/mail/encodings/7bit.rb +1 -0
  32. data/lib/mail/encodings/8bit.rb +8 -2
  33. data/lib/mail/encodings/base64.rb +7 -1
  34. data/lib/mail/encodings/binary.rb +1 -0
  35. data/lib/mail/encodings/quoted_printable.rb +11 -4
  36. data/lib/mail/encodings/transfer_encoding.rb +27 -17
  37. data/lib/mail/encodings/unix_to_unix.rb +18 -0
  38. data/lib/mail/encodings.rb +54 -39
  39. data/lib/mail/envelope.rb +1 -5
  40. data/lib/mail/field.rb +81 -31
  41. data/lib/mail/field_list.rb +19 -18
  42. data/lib/mail/fields/bcc_field.rb +17 -5
  43. data/lib/mail/fields/cc_field.rb +3 -3
  44. data/lib/mail/fields/comments_field.rb +2 -1
  45. data/lib/mail/fields/common/address_container.rb +1 -0
  46. data/lib/mail/fields/common/common_address.rb +34 -28
  47. data/lib/mail/fields/common/common_date.rb +2 -8
  48. data/lib/mail/fields/common/common_field.rb +9 -5
  49. data/lib/mail/fields/common/common_message_id.rb +3 -2
  50. data/lib/mail/fields/common/parameter_hash.rb +4 -3
  51. data/lib/mail/fields/content_description_field.rb +1 -0
  52. data/lib/mail/fields/content_disposition_field.rb +13 -12
  53. data/lib/mail/fields/content_id_field.rb +4 -4
  54. data/lib/mail/fields/content_location_field.rb +2 -1
  55. data/lib/mail/fields/content_transfer_encoding_field.rb +4 -9
  56. data/lib/mail/fields/content_type_field.rb +4 -3
  57. data/lib/mail/fields/date_field.rb +16 -15
  58. data/lib/mail/fields/from_field.rb +3 -3
  59. data/lib/mail/fields/in_reply_to_field.rb +3 -1
  60. data/lib/mail/fields/keywords_field.rb +2 -2
  61. data/lib/mail/fields/message_id_field.rb +4 -4
  62. data/lib/mail/fields/mime_version_field.rb +3 -2
  63. data/lib/mail/fields/optional_field.rb +1 -0
  64. data/lib/mail/fields/received_field.rb +4 -3
  65. data/lib/mail/fields/references_field.rb +3 -1
  66. data/lib/mail/fields/reply_to_field.rb +3 -3
  67. data/lib/mail/fields/resent_bcc_field.rb +3 -3
  68. data/lib/mail/fields/resent_cc_field.rb +3 -3
  69. data/lib/mail/fields/resent_date_field.rb +2 -1
  70. data/lib/mail/fields/resent_from_field.rb +3 -3
  71. data/lib/mail/fields/resent_message_id_field.rb +1 -0
  72. data/lib/mail/fields/resent_sender_field.rb +4 -4
  73. data/lib/mail/fields/resent_to_field.rb +3 -3
  74. data/lib/mail/fields/return_path_field.rb +1 -1
  75. data/lib/mail/fields/sender_field.rb +9 -9
  76. data/lib/mail/fields/structured_field.rb +1 -0
  77. data/lib/mail/fields/subject_field.rb +1 -0
  78. data/lib/mail/fields/to_field.rb +3 -3
  79. data/lib/mail/fields/unstructured_field.rb +15 -8
  80. data/lib/mail/fields.rb +1 -0
  81. data/lib/mail/header.rb +21 -29
  82. data/lib/mail/indifferent_hash.rb +1 -0
  83. data/lib/mail/mail.rb +16 -1
  84. data/lib/mail/matchers/attachment_matchers.rb +29 -0
  85. data/lib/mail/matchers/has_sent_mail.rb +61 -4
  86. data/lib/mail/message.rb +175 -72
  87. data/lib/mail/multibyte/chars.rb +3 -2
  88. data/lib/mail/multibyte/exceptions.rb +1 -0
  89. data/lib/mail/multibyte/unicode.rb +22 -15
  90. data/lib/mail/multibyte/utils.rb +1 -0
  91. data/lib/mail/multibyte.rb +1 -0
  92. data/lib/mail/network/delivery_methods/exim.rb +3 -7
  93. data/lib/mail/network/delivery_methods/file_delivery.rb +2 -1
  94. data/lib/mail/network/delivery_methods/sendmail.rb +33 -10
  95. data/lib/mail/network/delivery_methods/smtp.rb +32 -34
  96. data/lib/mail/network/delivery_methods/smtp_connection.rb +7 -6
  97. data/lib/mail/network/delivery_methods/test_mailer.rb +3 -2
  98. data/lib/mail/network/retriever_methods/base.rb +1 -0
  99. data/lib/mail/network/retriever_methods/imap.rb +19 -13
  100. data/lib/mail/network/retriever_methods/pop3.rb +1 -0
  101. data/lib/mail/network/retriever_methods/test_retriever.rb +1 -0
  102. data/lib/mail/network.rb +1 -0
  103. data/lib/mail/parsers/address_lists_parser.rb +133 -0
  104. data/lib/mail/parsers/content_disposition_parser.rb +68 -0
  105. data/lib/mail/parsers/content_location_parser.rb +36 -0
  106. data/lib/mail/parsers/content_transfer_encoding_parser.rb +34 -0
  107. data/lib/mail/parsers/content_type_parser.rb +67 -0
  108. data/lib/mail/parsers/date_time_parser.rb +39 -0
  109. data/lib/mail/parsers/envelope_from_parser.rb +46 -0
  110. data/lib/mail/parsers/message_ids_parser.rb +40 -0
  111. data/lib/mail/parsers/mime_version_parser.rb +42 -0
  112. data/lib/mail/parsers/phrase_lists_parser.rb +36 -0
  113. data/lib/mail/parsers/ragel/common.rl +185 -0
  114. data/lib/mail/parsers/ragel/date_time.rl +30 -0
  115. data/lib/mail/parsers/ragel/parser_info.rb +61 -0
  116. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +14864 -0
  117. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +37 -0
  118. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +751 -0
  119. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +37 -0
  120. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +614 -0
  121. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +37 -0
  122. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +447 -0
  123. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +37 -0
  124. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +825 -0
  125. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +37 -0
  126. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +817 -0
  127. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +37 -0
  128. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +2149 -0
  129. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +37 -0
  130. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +1570 -0
  131. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +37 -0
  132. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +440 -0
  133. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +37 -0
  134. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +564 -0
  135. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +37 -0
  136. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +51 -0
  137. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +5144 -0
  138. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +37 -0
  139. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +37 -0
  140. data/lib/mail/parsers/ragel/ruby.rb +40 -0
  141. data/lib/mail/parsers/ragel.rb +18 -0
  142. data/lib/mail/parsers/received_parser.rb +49 -0
  143. data/lib/mail/parsers.rb +27 -0
  144. data/lib/mail/part.rb +11 -4
  145. data/lib/mail/parts_list.rb +30 -10
  146. data/lib/mail/utilities.rb +127 -63
  147. data/lib/mail/values/unicode_tables.dat +0 -0
  148. data/lib/mail/version.rb +8 -15
  149. data/lib/mail/version_specific/ruby_1_8.rb +18 -7
  150. data/lib/mail/version_specific/ruby_1_9.rb +136 -32
  151. data/lib/mail.rb +12 -6
  152. metadata +115 -77
  153. data/lib/VERSION +0 -4
  154. data/lib/load_parsers.rb +0 -41
  155. data/lib/mail/core_extensions/nil.rb +0 -19
  156. data/lib/mail/core_extensions/object.rb +0 -13
  157. data/lib/mail/core_extensions/shell_escape.rb +0 -56
  158. data/lib/mail/parsers/address_lists.rb +0 -64
  159. data/lib/mail/parsers/address_lists.treetop +0 -19
  160. data/lib/mail/parsers/content_disposition.rb +0 -535
  161. data/lib/mail/parsers/content_disposition.treetop +0 -46
  162. data/lib/mail/parsers/content_location.rb +0 -139
  163. data/lib/mail/parsers/content_location.treetop +0 -20
  164. data/lib/mail/parsers/content_transfer_encoding.rb +0 -162
  165. data/lib/mail/parsers/content_transfer_encoding.treetop +0 -20
  166. data/lib/mail/parsers/content_type.rb +0 -967
  167. data/lib/mail/parsers/content_type.treetop +0 -68
  168. data/lib/mail/parsers/date_time.rb +0 -114
  169. data/lib/mail/parsers/date_time.treetop +0 -11
  170. data/lib/mail/parsers/envelope_from.rb +0 -194
  171. data/lib/mail/parsers/envelope_from.treetop +0 -32
  172. data/lib/mail/parsers/message_ids.rb +0 -45
  173. data/lib/mail/parsers/message_ids.treetop +0 -15
  174. data/lib/mail/parsers/mime_version.rb +0 -144
  175. data/lib/mail/parsers/mime_version.treetop +0 -19
  176. data/lib/mail/parsers/phrase_lists.rb +0 -45
  177. data/lib/mail/parsers/phrase_lists.treetop +0 -15
  178. data/lib/mail/parsers/received.rb +0 -71
  179. data/lib/mail/parsers/received.treetop +0 -11
  180. data/lib/mail/parsers/rfc2045.rb +0 -464
  181. data/lib/mail/parsers/rfc2045.treetop +0 -36
  182. data/lib/mail/parsers/rfc2822.rb +0 -5397
  183. data/lib/mail/parsers/rfc2822.treetop +0 -410
  184. data/lib/mail/parsers/rfc2822_obsolete.rb +0 -3768
  185. data/lib/mail/parsers/rfc2822_obsolete.treetop +0 -241
  186. data/lib/tasks/corpus.rake +0 -125
  187. data/lib/tasks/treetop.rake +0 -10
@@ -1,30 +1,21 @@
1
+ # frozen_string_literal: true
1
2
  module Mail
2
-
3
3
  module CheckDeliveryParams
4
+ def check_delivery_params(mail)
5
+ if Utilities.blank?(mail.smtp_envelope_from)
6
+ raise ArgumentError.new('An SMTP From address is required to send a message. Set the message smtp_envelope_from, return_path, sender, or from address.')
7
+ end
4
8
 
5
- def self.included(klass)
6
- klass.class_eval do
7
-
8
- def check_params(mail)
9
- envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
10
- if envelope_from.blank?
11
- raise ArgumentError.new('A sender (Return-Path, Sender or From) required to send a message')
12
- end
13
-
14
- destinations ||= mail.destinations if mail.respond_to?(:destinations) && mail.destinations
15
- if destinations.blank?
16
- raise ArgumentError.new('At least one recipient (To, Cc or Bcc) is required to send a message')
17
- end
18
-
19
- message ||= mail.encoded if mail.respond_to?(:encoded)
20
- if message.blank?
21
- raise ArgumentError.new('A encoded content is required to send a message')
22
- end
23
-
24
- [envelope_from, destinations, message]
25
- end
9
+ if Utilities.blank?(mail.smtp_envelope_to)
10
+ raise ArgumentError.new('An SMTP To address is required to send a message. Set the message smtp_envelope_to, to, cc, or bcc address.')
11
+ end
26
12
 
13
+ message = mail.encoded if mail.respond_to?(:encoded)
14
+ if Utilities.blank?(message)
15
+ raise ArgumentError.new('An encoded message is required to send an email')
27
16
  end
17
+
18
+ [mail.smtp_envelope_from, mail.smtp_envelope_to, message]
28
19
  end
29
20
  end
30
21
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # Thanks to Nicolas Fouché for this wrapper
4
5
  #
@@ -1,28 +1,32 @@
1
1
  # encoding: us-ascii
2
+ # frozen_string_literal: true
2
3
  module Mail
3
- module Patterns
4
+ module Constants
4
5
  white_space = %Q|\x9\x20|
5
6
  text = %Q|\x1-\x8\xB\xC\xE-\x7f|
6
7
  field_name = %Q|\x21-\x39\x3b-\x7e|
7
8
  qp_safe = %Q|\x20-\x3c\x3e-\x7e|
8
-
9
+
9
10
  aspecial = %Q|()<>[]:;@\\,."| # RFC5322
10
11
  tspecial = %Q|()<>@,;:\\"/[]?=| # RFC2045
11
12
  sp = %Q| |
12
13
  control = %Q|\x00-\x1f\x7f-\xff|
13
-
14
+
14
15
  if control.respond_to?(:force_encoding)
15
- control = control.force_encoding(Encoding::BINARY)
16
+ control = control.dup.force_encoding(Encoding::BINARY)
16
17
  end
17
-
18
+
18
19
  CRLF = /\r\n/
19
20
  WSP = /[#{white_space}]/
20
21
  FWS = /#{CRLF}#{WSP}*/
21
22
  TEXT = /[#{text}]/ # + obs-text
22
23
  FIELD_NAME = /[#{field_name}]+/
23
- FIELD_BODY = /.+/
24
+ FIELD_PREFIX = /\A(#{FIELD_NAME})/
25
+ FIELD_BODY = /.+/m
24
26
  FIELD_LINE = /^[#{field_name}]+:\s*.+$/
27
+ FIELD_SPLIT = /^(#{FIELD_NAME})\s*:\s*(#{FIELD_BODY})?$/
25
28
  HEADER_LINE = /^([#{field_name}]+:\s*.+)$/
29
+ HEADER_SPLIT = /#{CRLF}(?!#{WSP})/
26
30
 
27
31
  QP_UNSAFE = /[^#{qp_safe}]/
28
32
  QP_SAFE = /[#{qp_safe}]/
@@ -30,5 +34,24 @@ module Mail
30
34
  ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{sp}]/n
31
35
  PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
32
36
  TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{sp}]/n
37
+ ENCODED_VALUE = /\=\?([^?]+)\?([QB])\?[^?]*?\?\=/mi
38
+ FULL_ENCODED_VALUE = /(\=\?[^?]+\?[QB]\?[^?]*?\?\=)/mi
39
+
40
+ EMPTY = ''
41
+ SPACE = ' '
42
+ UNDERSCORE = '_'
43
+ HYPHEN = '-'
44
+ COLON = ':'
45
+ ASTERISK = '*'
46
+ CR = "\r"
47
+ LF = "\n"
48
+ CR_ENCODED = "=0D"
49
+ LF_ENCODED = "=0A"
50
+ CAPITAL_M = 'M'
51
+ EQUAL_LF = "=\n"
52
+ NULL_SENDER = '<>'
53
+
54
+ Q_VALUES = ['Q','q']
55
+ B_VALUES = ['B','b']
33
56
  end
34
57
  end
@@ -1,25 +1,25 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Net
3
4
  class SMTP
4
5
  # This is a backport of r30294 from ruby trunk because of a bug in net/smtp.
5
6
  # http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&amp;revision=30294
6
7
  #
7
8
  # Fixed in what will be Ruby 1.9.3 - tlsconnect also does not exist in some early versions of ruby
8
- remove_method :tlsconnect if defined?(Net::SMTP.new.tlsconnect)
9
+ begin
10
+ alias_method :original_tlsconnect, :tlsconnect
9
11
 
10
- def tlsconnect(s)
11
- verified = false
12
- s = OpenSSL::SSL::SSLSocket.new s, @ssl_context
13
- logging "TLS connection started"
14
- s.sync_close = true
15
- s.connect
16
- if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
17
- s.post_connection_check(@address)
12
+ def tlsconnect(s)
13
+ verified = false
14
+ begin
15
+ original_tlsconnect(s).tap { verified = true }
16
+ ensure
17
+ unless verified
18
+ s.close rescue nil
19
+ end
20
+ end
18
21
  end
19
- verified = true
20
- s
21
- ensure
22
- s.close unless verified
22
+ rescue NameError
23
23
  end
24
24
  end
25
25
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  # This is not loaded if ActiveSupport is already loaded
4
5
 
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  # This is not loaded if ActiveSupport is already loaded
4
5
 
@@ -1,26 +1,14 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  class String #:nodoc:
3
- def to_crlf
4
- to_str.gsub(/\n|\r\n|\r/) { "\r\n" }
5
- end
6
-
7
- def to_lf
8
- to_str.gsub(/\n|\r\n|\r/) { "\n" }
9
- end
10
-
11
- unless String.instance_methods(false).map {|m| m.to_sym}.include?(:blank?)
12
- def blank?
13
- self !~ /\S/
14
- end
15
- end
16
4
 
17
5
  unless method_defined?(:ascii_only?)
18
- # Provides all strings with the Ruby 1.9 method of .ascii_only? and
19
- # returns true or false
20
- US_ASCII_REGEXP = %Q{\x00-\x7f}
6
+ # Backport from Ruby 1.9 checks for non-us-ascii characters.
21
7
  def ascii_only?
22
- !(self =~ /[^#{US_ASCII_REGEXP}]/)
8
+ self !~ MATCH_NON_US_ASCII
23
9
  end
10
+
11
+ MATCH_NON_US_ASCII = /[^\x00-\x7f]/
24
12
  end
25
13
 
26
14
  def not_ascii_only?
@@ -1,17 +1,18 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class Address
4
-
5
+
5
6
  include Mail::Utilities
6
-
7
+
7
8
  # Mail::Address handles all email addresses in Mail. It takes an email address string
8
- # and parses it, breaking it down into it's component parts and allowing you to get the
9
+ # and parses it, breaking it down into its component parts and allowing you to get the
9
10
  # address, comments, display name, name, local part, domain part and fully formatted
10
11
  # address.
11
- #
12
+ #
12
13
  # Mail::Address requires a correctly formatted email address per RFC2822 or RFC822. It
13
14
  # handles all obsolete versions including obsolete domain routing on the local part.
14
- #
15
+ #
15
16
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
16
17
  # a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
17
18
  # a.address #=> 'mikel@test.lindsaar.net'
@@ -21,26 +22,23 @@ module Mail
21
22
  # a.comments #=> ['My email address']
22
23
  # a.to_s #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
23
24
  def initialize(value = nil)
24
- @output_type = nil
25
- @tree = nil
26
- @raw_text = value
27
- case
28
- when value.nil?
25
+ @output_type = :decode
26
+ if value.nil?
29
27
  @parsed = false
30
- return
28
+ @data = nil
31
29
  else
32
30
  parse(value)
33
31
  end
34
32
  end
35
-
36
- # Returns the raw imput of the passed in string, this is before it is passed
33
+
34
+ # Returns the raw input of the passed in string, this is before it is passed
37
35
  # by the parser.
38
36
  def raw
39
- @raw_text
37
+ @data.raw
40
38
  end
41
39
 
42
40
  # Returns a correctly formatted address for the email going out. If given
43
- # an incorrectly formatted address as input, Mail::Address will do it's best
41
+ # an incorrectly formatted address as input, Mail::Address will do its best
44
42
  # to format it correctly. This includes quoting display names as needed and
45
43
  # putting the address in angle brackets etc.
46
44
  #
@@ -48,37 +46,38 @@ module Mail
48
46
  # a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
49
47
  def format
50
48
  parse unless @parsed
51
- case
52
- when tree.nil?
53
- ''
54
- when display_name
55
- [quote_phrase(display_name), "<#{address}>", format_comments].compact.join(" ")
49
+ if @data.nil?
50
+ EMPTY
51
+ elsif display_name
52
+ [quote_phrase(display_name), "<#{address}>", format_comments].compact.join(SPACE)
53
+ elsif address
54
+ [address, format_comments].compact.join(SPACE)
56
55
  else
57
- [address, format_comments].compact.join(" ")
56
+ raw
58
57
  end
59
58
  end
60
59
 
61
- # Returns the address that is in the address itself. That is, the
60
+ # Returns the address that is in the address itself. That is, the
62
61
  # local@domain string, without any angle brackets or the like.
63
- #
62
+ #
64
63
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
65
64
  # a.address #=> 'mikel@test.lindsaar.net'
66
65
  def address
67
66
  parse unless @parsed
68
67
  domain ? "#{local}@#{domain}" : local
69
68
  end
70
-
69
+
71
70
  # Provides a way to assign an address to an already made Mail::Address object.
72
- #
71
+ #
73
72
  # a = Address.new
74
73
  # a.address = 'Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>'
75
74
  # a.address #=> 'mikel@test.lindsaar.net'
76
75
  def address=(value)
77
76
  parse(value)
78
77
  end
79
-
78
+
80
79
  # Returns the display name of the email address passed in.
81
- #
80
+ #
82
81
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
83
82
  # a.display_name #=> 'Mikel Lindsaar'
84
83
  def display_name
@@ -86,70 +85,66 @@ module Mail
86
85
  @display_name ||= get_display_name
87
86
  Encodings.decode_encode(@display_name.to_s, @output_type) if @display_name
88
87
  end
89
-
88
+
90
89
  # Provides a way to assign a display name to an already made Mail::Address object.
91
- #
90
+ #
92
91
  # a = Address.new
93
92
  # a.address = 'mikel@test.lindsaar.net'
94
93
  # a.display_name = 'Mikel Lindsaar'
95
94
  # a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>'
96
95
  def display_name=( str )
97
- @display_name = str
96
+ @display_name = str.dup # in case frozen
98
97
  end
99
98
 
100
99
  # Returns the local part (the left hand side of the @ sign in the email address) of
101
100
  # the address
102
- #
101
+ #
103
102
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
104
103
  # a.local #=> 'mikel'
105
104
  def local
106
105
  parse unless @parsed
107
- "#{obs_domain_list}#{get_local.strip}" if get_local
106
+ Encodings.decode_encode("#{@data.obs_domain_list}#{get_local.strip}", @output_type) if get_local
108
107
  end
109
108
 
110
109
  # Returns the domain part (the right hand side of the @ sign in the email address) of
111
110
  # the address
112
- #
111
+ #
113
112
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
114
113
  # a.domain #=> 'test.lindsaar.net'
115
114
  def domain
116
115
  parse unless @parsed
117
- strip_all_comments(get_domain) if get_domain
116
+ Encodings.decode_encode(strip_all_comments(get_domain), @output_type) if get_domain
118
117
  end
119
-
118
+
120
119
  # Returns an array of comments that are in the email, or an empty array if there
121
120
  # are no comments
122
- #
121
+ #
123
122
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
124
123
  # a.comments #=> ['My email address']
125
124
  def comments
126
125
  parse unless @parsed
127
- if get_comments.empty?
128
- nil
129
- else
130
- get_comments.map { |c| c.squeeze(" ") }
131
- end
126
+ get_comments.map { |c| c.squeeze(SPACE) } unless get_comments.empty?
132
127
  end
133
-
128
+
134
129
  # Sometimes an address will not have a display name, but might have the name
135
130
  # as a comment field after the address. This returns that name if it exists.
136
- #
131
+ #
137
132
  # a = Address.new('mikel@test.lindsaar.net (Mikel Lindsaar)')
138
133
  # a.name #=> 'Mikel Lindsaar'
139
134
  def name
140
135
  parse unless @parsed
141
136
  get_name
142
137
  end
143
-
138
+
144
139
  # Returns the format of the address, or returns nothing
145
- #
140
+ #
146
141
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
147
142
  # a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
148
143
  def to_s
149
144
  parse unless @parsed
150
145
  format
151
146
  end
152
-
147
+
153
148
  # Shows the Address object basic details, including the Address
154
149
  # a = Address.new('Mikel (My email) <mikel@test.lindsaar.net>')
155
150
  # a.inspect #=> "#<Mail::Address:14184910 Address: |Mikel <mikel@test.lindsaar.net> (My email)| >"
@@ -157,150 +152,96 @@ module Mail
157
152
  parse unless @parsed
158
153
  "#<#{self.class}:#{self.object_id} Address: |#{to_s}| >"
159
154
  end
160
-
155
+
161
156
  def encoded
162
157
  @output_type = :encode
163
158
  format
164
159
  end
165
-
160
+
166
161
  def decoded
167
162
  @output_type = :decode
168
163
  format
169
164
  end
170
165
 
166
+ def group
167
+ @data && @data.group
168
+ end
169
+
171
170
  private
172
-
171
+
173
172
  def parse(value = nil)
174
173
  @parsed = true
175
- case
176
- when value.nil?
177
- nil
178
- when value.class == String
179
- self.tree = Mail::AddressList.new(value).address_nodes.first
180
- else
181
- self.tree = value
182
- end
183
- end
184
-
185
-
186
- def get_domain
187
- if tree.respond_to?(:angle_addr) && tree.angle_addr.respond_to?(:addr_spec) && tree.angle_addr.addr_spec.respond_to?(:domain)
188
- @domain_text ||= tree.angle_addr.addr_spec.domain.text_value.strip
189
- elsif tree.respond_to?(:domain)
190
- @domain_text ||= tree.domain.text_value.strip
191
- elsif tree.respond_to?(:addr_spec) && tree.addr_spec.respond_to?(:domain)
192
- tree.addr_spec.domain.text_value.strip
193
- else
194
- nil
174
+ @data = nil
175
+
176
+ case value
177
+ when Mail::Parsers::AddressStruct
178
+ @data = value
179
+ when String
180
+ unless Utilities.blank?(value)
181
+ address_list = Mail::Parsers::AddressListsParser.new.parse(value)
182
+ @data = address_list.addresses.first
183
+ end
195
184
  end
196
185
  end
197
186
 
198
187
  def strip_all_comments(string)
199
- unless comments.blank?
188
+ unless Utilities.blank?(comments)
200
189
  comments.each do |comment|
201
- string = string.gsub("(#{comment})", '')
190
+ string = string.gsub("(#{comment})", EMPTY)
202
191
  end
203
192
  end
204
193
  string.strip
205
194
  end
206
195
 
207
196
  def strip_domain_comments(value)
208
- unless comments.blank?
197
+ unless Utilities.blank?(comments)
209
198
  comments.each do |comment|
210
- if get_domain && get_domain.include?("(#{comment})")
211
- value = value.gsub("(#{comment})", '')
199
+ if @data.domain && @data.domain.include?("(#{comment})")
200
+ value = value.gsub("(#{comment})", EMPTY)
212
201
  end
213
202
  end
214
203
  end
215
204
  value.to_s.strip
216
205
  end
217
-
218
- def get_comments
219
- if tree.respond_to?(:comments)
220
- @comments = tree.comments.map { |c| unparen(c.text_value.to_str) }
221
- else
222
- @comments = []
223
- end
224
- end
225
-
206
+
226
207
  def get_display_name
227
- if tree.respond_to?(:display_name)
228
- name = unquote(tree.display_name.text_value.strip)
229
- str = strip_all_comments(name.to_s)
230
- elsif comments
231
- if domain
232
- str = strip_domain_comments(format_comments)
233
- else
234
- str = nil
235
- end
236
- else
237
- nil
238
- end
239
-
240
- if str.blank?
241
- nil
242
- else
243
- str
208
+ if @data.display_name
209
+ str = strip_all_comments(@data.display_name.to_s)
210
+ elsif @data.comments && @data.domain
211
+ str = strip_domain_comments(format_comments)
244
212
  end
213
+ str unless Utilities.blank?(str)
245
214
  end
246
-
215
+
247
216
  def get_name
248
217
  if display_name
249
218
  str = display_name
250
- else
251
- if comments
252
- comment_text = comments.join(' ').squeeze(" ")
253
- str = "(#{comment_text})"
254
- end
219
+ elsif comments
220
+ str = "(#{comments.join(SPACE).squeeze(SPACE)})"
255
221
  end
256
222
 
257
- if str.blank?
258
- nil
259
- else
260
- unparen(str)
261
- end
262
- end
263
-
264
- # Provides access to the Treetop parse tree for this address
265
- def tree
266
- @tree
223
+ unparen(str) unless Utilities.blank?(str)
267
224
  end
268
-
269
- def tree=(value)
270
- @tree = value
271
- end
272
-
225
+
273
226
  def format_comments
274
227
  if comments
275
- comment_text = comments.map {|c| escape_paren(c) }.join(' ').squeeze(" ")
228
+ comment_text = comments.map {|c| escape_paren(c) }.join(SPACE).squeeze(SPACE)
276
229
  @format_comments ||= "(#{comment_text})"
277
230
  else
278
231
  nil
279
232
  end
280
233
  end
281
-
282
- def obs_domain_list
283
- if tree.respond_to?(:angle_addr)
284
- obs = tree.angle_addr.elements.select { |e| e.respond_to?(:obs_domain_list) }
285
- !obs.empty? ? obs.first.text_value : nil
286
- else
287
- nil
288
- end
289
- end
290
-
234
+
291
235
  def get_local
292
- case
293
- when tree.respond_to?(:local_dot_atom_text)
294
- tree.local_dot_atom_text.text_value
295
- when tree.respond_to?(:angle_addr) && tree.angle_addr.respond_to?(:addr_spec) && tree.angle_addr.addr_spec.respond_to?(:local_part)
296
- tree.angle_addr.addr_spec.local_part.text_value
297
- when tree.respond_to?(:addr_spec) && tree.addr_spec.respond_to?(:local_part)
298
- tree.addr_spec.local_part.text_value
299
- else
300
- tree && tree.respond_to?(:local_part) ? tree.local_part.text_value : nil
301
- end
236
+ @data && @data.local
237
+ end
238
+
239
+ def get_domain
240
+ @data && @data.domain
241
+ end
242
+
243
+ def get_comments
244
+ @data && @data.comments
302
245
  end
303
-
304
-
305
246
  end
306
- end
247
+ end
@@ -1,7 +1,8 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class AddressList # :nodoc:
4
-
5
+
5
6
  # Mail::AddressList is the class that parses To, From and other address fields from
6
7
  # emails passed into Mail.
7
8
  #
@@ -18,57 +19,24 @@ module Mail
18
19
  # a.addresses #=> [#<Mail::Address:14943130 Address: |ada@test.lindsaar.net...
19
20
  # a.group_names #=> ["My Group"]
20
21
  def initialize(string)
21
- if string.blank?
22
- @address_nodes = []
23
- return self
24
- end
25
- parser = Mail::AddressListsParser.new
26
- if tree = parser.parse(string)
27
- @address_nodes = tree.addresses
28
- else
29
- raise Mail::Field::ParseError.new(AddressListsParser, string, parser.failure_reason)
30
- end
22
+ @addresses_grouped_by_group = nil
23
+ @address_list = Parsers::AddressListsParser.new.parse(string)
31
24
  end
32
25
 
33
26
  # Returns a list of address objects from the parsed line
34
27
  def addresses
35
- @addresses ||= get_addresses.map do |address_tree|
36
- Mail::Address.new(address_tree)
28
+ @addresses ||= @address_list.addresses.map do |address_data|
29
+ Mail::Address.new(address_data)
37
30
  end
38
31
  end
39
-
40
- # Returns a list of all recipient syntax trees that are not part of a group
41
- def individual_recipients # :nodoc:
42
- @individual_recipients ||= @address_nodes - group_recipients
43
- end
44
-
45
- # Returns a list of all recipient syntax trees that are part of a group
46
- def group_recipients # :nodoc:
47
- @group_recipients ||= @address_nodes.select { |an| an.respond_to?(:group_name) }
32
+
33
+ def addresses_grouped_by_group
34
+ addresses.select(&:group).group_by(&:group)
48
35
  end
49
36
 
50
37
  # Returns the names as an array of strings of all groups
51
38
  def group_names # :nodoc:
52
- group_recipients.map { |g| g.group_name.text_value }
53
- end
54
-
55
- # Returns a list of address syntax trees
56
- def address_nodes # :nodoc:
57
- @address_nodes
58
- end
59
-
60
- private
61
-
62
- def get_addresses
63
- (individual_recipients + group_recipients.map { |g| get_group_addresses(g) }).flatten
64
- end
65
-
66
- def get_group_addresses(g)
67
- if g.group_list.respond_to?(:addresses)
68
- g.group_list.addresses
69
- else
70
- []
71
- end
39
+ @address_list.group_names
72
40
  end
73
41
  end
74
42
  end