mail 2.6.1 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +92 -80
  4. data/lib/mail/attachments_list.rb +11 -5
  5. data/lib/mail/body.rb +81 -44
  6. data/lib/mail/check_delivery_params.rb +50 -10
  7. data/lib/mail/configuration.rb +3 -0
  8. data/lib/mail/{patterns.rb → constants.rb} +26 -6
  9. data/lib/mail/core_extensions/smtp.rb +20 -16
  10. data/lib/mail/core_extensions/string.rb +1 -27
  11. data/lib/mail/elements/address.rb +81 -93
  12. data/lib/mail/elements/address_list.rb +12 -29
  13. data/lib/mail/elements/content_disposition_element.rb +9 -15
  14. data/lib/mail/elements/content_location_element.rb +8 -12
  15. data/lib/mail/elements/content_transfer_encoding_element.rb +6 -10
  16. data/lib/mail/elements/content_type_element.rb +9 -19
  17. data/lib/mail/elements/date_time_element.rb +7 -14
  18. data/lib/mail/elements/envelope_from_element.rb +15 -21
  19. data/lib/mail/elements/message_ids_element.rb +12 -14
  20. data/lib/mail/elements/mime_version_element.rb +7 -14
  21. data/lib/mail/elements/phrase_list.rb +7 -9
  22. data/lib/mail/elements/received_element.rb +10 -15
  23. data/lib/mail/elements.rb +1 -0
  24. data/lib/mail/encodings/7bit.rb +6 -15
  25. data/lib/mail/encodings/8bit.rb +5 -18
  26. data/lib/mail/encodings/base64.rb +15 -10
  27. data/lib/mail/encodings/binary.rb +4 -22
  28. data/lib/mail/encodings/identity.rb +24 -0
  29. data/lib/mail/encodings/quoted_printable.rb +13 -7
  30. data/lib/mail/encodings/transfer_encoding.rb +47 -28
  31. data/lib/mail/encodings/unix_to_unix.rb +20 -0
  32. data/lib/mail/encodings.rb +121 -82
  33. data/lib/mail/envelope.rb +2 -1
  34. data/lib/mail/field.rb +114 -62
  35. data/lib/mail/field_list.rb +2 -1
  36. data/lib/mail/fields/bcc_field.rb +17 -5
  37. data/lib/mail/fields/cc_field.rb +2 -2
  38. data/lib/mail/fields/comments_field.rb +2 -1
  39. data/lib/mail/fields/common/address_container.rb +3 -2
  40. data/lib/mail/fields/common/common_address.rb +40 -14
  41. data/lib/mail/fields/common/common_date.rb +2 -1
  42. data/lib/mail/fields/common/common_field.rb +6 -11
  43. data/lib/mail/fields/common/common_message_id.rb +3 -2
  44. data/lib/mail/fields/common/parameter_hash.rb +5 -4
  45. data/lib/mail/fields/content_description_field.rb +2 -1
  46. data/lib/mail/fields/content_disposition_field.rb +14 -13
  47. data/lib/mail/fields/content_id_field.rb +5 -4
  48. data/lib/mail/fields/content_location_field.rb +3 -2
  49. data/lib/mail/fields/content_transfer_encoding_field.rb +3 -2
  50. data/lib/mail/fields/content_type_field.rb +7 -11
  51. data/lib/mail/fields/date_field.rb +4 -4
  52. data/lib/mail/fields/from_field.rb +2 -2
  53. data/lib/mail/fields/in_reply_to_field.rb +2 -1
  54. data/lib/mail/fields/keywords_field.rb +3 -3
  55. data/lib/mail/fields/message_id_field.rb +3 -2
  56. data/lib/mail/fields/mime_version_field.rb +4 -3
  57. data/lib/mail/fields/optional_field.rb +5 -1
  58. data/lib/mail/fields/received_field.rb +5 -4
  59. data/lib/mail/fields/references_field.rb +2 -1
  60. data/lib/mail/fields/reply_to_field.rb +2 -2
  61. data/lib/mail/fields/resent_bcc_field.rb +2 -2
  62. data/lib/mail/fields/resent_cc_field.rb +2 -2
  63. data/lib/mail/fields/resent_date_field.rb +2 -2
  64. data/lib/mail/fields/resent_from_field.rb +2 -2
  65. data/lib/mail/fields/resent_message_id_field.rb +2 -1
  66. data/lib/mail/fields/resent_sender_field.rb +2 -2
  67. data/lib/mail/fields/resent_to_field.rb +2 -2
  68. data/lib/mail/fields/return_path_field.rb +2 -2
  69. data/lib/mail/fields/sender_field.rb +2 -2
  70. data/lib/mail/fields/structured_field.rb +1 -0
  71. data/lib/mail/fields/subject_field.rb +2 -1
  72. data/lib/mail/fields/to_field.rb +2 -2
  73. data/lib/mail/fields/unstructured_field.rb +28 -10
  74. data/lib/mail/fields.rb +1 -0
  75. data/lib/mail/header.rb +18 -14
  76. data/lib/mail/indifferent_hash.rb +1 -0
  77. data/lib/mail/mail.rb +6 -11
  78. data/lib/mail/matchers/attachment_matchers.rb +29 -0
  79. data/lib/mail/matchers/has_sent_mail.rb +53 -9
  80. data/lib/mail/message.rb +99 -89
  81. data/lib/mail/multibyte/chars.rb +32 -30
  82. data/lib/mail/multibyte/unicode.rb +31 -26
  83. data/lib/mail/multibyte/utils.rb +1 -0
  84. data/lib/mail/multibyte.rb +65 -15
  85. data/lib/mail/network/delivery_methods/exim.rb +7 -10
  86. data/lib/mail/network/delivery_methods/file_delivery.rb +5 -8
  87. data/lib/mail/network/delivery_methods/logger_delivery.rb +37 -0
  88. data/lib/mail/network/delivery_methods/sendmail.rb +17 -11
  89. data/lib/mail/network/delivery_methods/smtp.rb +60 -53
  90. data/lib/mail/network/delivery_methods/smtp_connection.rb +11 -6
  91. data/lib/mail/network/delivery_methods/test_mailer.rb +6 -8
  92. data/lib/mail/network/retriever_methods/base.rb +1 -0
  93. data/lib/mail/network/retriever_methods/imap.rb +19 -5
  94. data/lib/mail/network/retriever_methods/pop3.rb +4 -1
  95. data/lib/mail/network/retriever_methods/test_retriever.rb +2 -1
  96. data/lib/mail/network.rb +2 -0
  97. data/lib/mail/parser_tools.rb +15 -0
  98. data/lib/mail/parsers/address_lists_parser.rb +33208 -104
  99. data/lib/mail/parsers/address_lists_parser.rl +172 -0
  100. data/lib/mail/parsers/content_disposition_parser.rb +877 -49
  101. data/lib/mail/parsers/content_disposition_parser.rl +82 -0
  102. data/lib/mail/parsers/content_location_parser.rb +804 -23
  103. data/lib/mail/parsers/content_location_parser.rl +71 -0
  104. data/lib/mail/parsers/content_transfer_encoding_parser.rb +502 -19
  105. data/lib/mail/parsers/content_transfer_encoding_parser.rl +64 -0
  106. data/lib/mail/parsers/content_type_parser.rb +1024 -46
  107. data/lib/mail/parsers/content_type_parser.rl +83 -0
  108. data/lib/mail/parsers/date_time_parser.rb +872 -23
  109. data/lib/mail/parsers/date_time_parser.rl +62 -0
  110. data/lib/mail/parsers/envelope_from_parser.rb +3570 -34
  111. data/lib/mail/parsers/envelope_from_parser.rl +82 -0
  112. data/lib/mail/parsers/message_ids_parser.rb +2840 -25
  113. data/lib/mail/parsers/message_ids_parser.rl +82 -0
  114. data/lib/mail/parsers/mime_version_parser.rb +492 -26
  115. data/lib/mail/parsers/mime_version_parser.rl +61 -0
  116. data/lib/mail/parsers/phrase_lists_parser.rb +862 -17
  117. data/lib/mail/parsers/phrase_lists_parser.rl +83 -0
  118. data/lib/mail/parsers/received_parser.rb +8765 -36
  119. data/lib/mail/parsers/received_parser.rl +84 -0
  120. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  121. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  122. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  123. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  124. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  125. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  126. data/lib/mail/parsers/rfc5322.rl +59 -0
  127. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  128. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  129. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  130. data/lib/mail/parsers.rb +17 -24
  131. data/lib/mail/part.rb +8 -5
  132. data/lib/mail/parts_list.rb +31 -14
  133. data/lib/mail/utilities.rb +112 -13
  134. data/lib/mail/values/unicode_tables.dat +0 -0
  135. data/lib/mail/version.rb +8 -15
  136. data/lib/mail/version_specific/ruby_1_8.rb +52 -8
  137. data/lib/mail/version_specific/ruby_1_9.rb +143 -24
  138. data/lib/mail.rb +8 -14
  139. metadata +71 -81
  140. data/CHANGELOG.rdoc +0 -752
  141. data/CONTRIBUTING.md +0 -60
  142. data/Dependencies.txt +0 -2
  143. data/Gemfile +0 -15
  144. data/Rakefile +0 -29
  145. data/TODO.rdoc +0 -9
  146. data/VERSION +0 -4
  147. data/lib/mail/core_extensions/nil.rb +0 -19
  148. data/lib/mail/core_extensions/object.rb +0 -13
  149. data/lib/mail/core_extensions/string/access.rb +0 -145
  150. data/lib/mail/core_extensions/string/multibyte.rb +0 -78
  151. data/lib/mail/multibyte/exceptions.rb +0 -8
  152. data/lib/mail/parsers/ragel/common.rl +0 -184
  153. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  154. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  155. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  156. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  157. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  158. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  159. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  160. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  161. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  162. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  163. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  164. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  165. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  166. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2129
  167. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  168. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  169. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  170. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  171. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  172. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  173. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  174. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  175. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  176. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  177. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  178. data/lib/mail/parsers/ragel/ruby.rb +0 -39
  179. data/lib/mail/parsers/ragel.rb +0 -17
@@ -1,21 +1,17 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/parsers/content_location_parser'
4
+
2
5
  module Mail
3
6
  class ContentLocationElement # :nodoc:
4
-
5
- include Mail::Utilities
6
-
7
- def initialize( string )
8
- content_location = Mail::Parsers::ContentLocationParser.new.parse(string)
9
- @location = content_location.location
7
+ attr_reader :location
8
+
9
+ def initialize(string)
10
+ @location = Mail::Parsers::ContentLocationParser.parse(string).location
10
11
  end
11
-
12
- def location
13
- @location
14
- end
15
-
12
+
16
13
  def to_s(*args)
17
14
  location.to_s
18
15
  end
19
-
20
16
  end
21
17
  end
@@ -1,17 +1,13 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/parsers/content_transfer_encoding_parser'
4
+
2
5
  module Mail
3
6
  class ContentTransferEncodingElement
4
-
5
- include Mail::Utilities
6
-
7
+ attr_reader :encoding
8
+
7
9
  def initialize(string)
8
- content_transfer_encoding = Mail::Parsers::ContentTransferEncodingParser.new.parse(string)
9
- @encoding = content_transfer_encoding.encoding
10
+ @encoding = Mail::Parsers::ContentTransferEncodingParser.parse(string).encoding
10
11
  end
11
-
12
- def encoding
13
- @encoding
14
- end
15
-
16
12
  end
17
13
  end
@@ -1,31 +1,21 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/parsers/content_type_parser'
4
+
2
5
  module Mail
3
6
  class ContentTypeElement # :nodoc:
4
-
5
- include Mail::Utilities
6
-
7
- def initialize( string )
8
- content_type = Mail::Parsers::ContentTypeParser.new.parse(cleaned(string))
7
+ attr_reader :main_type, :sub_type, :parameters
8
+
9
+ def initialize(string)
10
+ content_type = Mail::Parsers::ContentTypeParser.parse(cleaned(string))
9
11
  @main_type = content_type.main_type
10
12
  @sub_type = content_type.sub_type
11
13
  @parameters = content_type.parameters
12
14
  end
13
-
14
- def main_type
15
- @main_type
16
- end
17
-
18
- def sub_type
19
- @sub_type
20
- end
21
-
22
- def parameters
23
- @parameters
24
- end
25
-
15
+
16
+ private
26
17
  def cleaned(string)
27
18
  string =~ /(.+);\s*$/ ? $1 : string
28
19
  end
29
-
30
20
  end
31
21
  end
@@ -1,22 +1,15 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/parsers/date_time_parser'
4
+
2
5
  module Mail
3
6
  class DateTimeElement # :nodoc:
4
-
5
- include Mail::Utilities
6
-
7
- def initialize( string )
8
- date_time = Mail::Parsers::DateTimeParser.new.parse(string)
7
+ attr_reader :date_string, :time_string
8
+
9
+ def initialize(string)
10
+ date_time = Mail::Parsers::DateTimeParser.parse(string)
9
11
  @date_string = date_time.date_string
10
12
  @time_string = date_time.time_string
11
13
  end
12
-
13
- def date_string
14
- @date_string
15
- end
16
-
17
- def time_string
18
- @time_string
19
- end
20
-
21
14
  end
22
15
  end
@@ -1,39 +1,33 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/parsers/envelope_from_parser'
4
+ require 'date'
5
+
2
6
  module Mail
3
7
  class EnvelopeFromElement
4
-
5
- include Mail::Utilities
6
-
7
- def initialize( string )
8
- @envelope_from = Mail::Parsers::EnvelopeFromParser.new.parse(string)
9
- @address = @envelope_from.address
10
- @date_time = ::DateTime.parse(@envelope_from.ctime_date)
11
- end
12
-
13
- def date_time
14
- @date_time
15
- end
16
-
17
- def address
18
- @address
8
+ attr_reader :date_time, :address
9
+
10
+ def initialize(string)
11
+ envelope_from = Mail::Parsers::EnvelopeFromParser.parse(string)
12
+ @address = envelope_from.address
13
+ @date_time = ::DateTime.parse(envelope_from.ctime_date)
19
14
  end
20
-
15
+
21
16
  # RFC 4155:
22
17
  # a timestamp indicating the UTC date and time when the message
23
18
  # was originally received, conformant with the syntax of the
24
19
  # traditional UNIX 'ctime' output sans timezone (note that the
25
20
  # use of UTC precludes the need for a timezone indicator);
26
21
  def formatted_date_time
27
- if @date_time.respond_to?(:ctime)
28
- @date_time.ctime
22
+ if date_time.respond_to?(:ctime)
23
+ date_time.ctime
29
24
  else
30
- @date_time.strftime '%a %b %e %T %Y'
25
+ date_time.strftime '%a %b %e %T %Y'
31
26
  end
32
27
  end
33
28
 
34
29
  def to_s
35
- "#{@address} #{formatted_date_time}"
30
+ "#{address} #{formatted_date_time}"
36
31
  end
37
-
38
32
  end
39
33
  end
@@ -1,24 +1,22 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/parsers/message_ids_parser'
4
+
2
5
  module Mail
3
6
  class MessageIdsElement
4
-
5
- include Mail::Utilities
6
-
7
+ attr_reader :message_ids
8
+
7
9
  def initialize(string)
8
- @message_ids = Mail::Parsers::MessageIdsParser.new.parse(string).message_ids.map { |msg_id| clean_msg_id(msg_id) }
9
- end
10
-
11
- def message_ids
12
- @message_ids
10
+ @message_ids = Mail::Parsers::MessageIdsParser.parse(string).message_ids.map { |msg_id| clean_msg_id(msg_id) }
13
11
  end
14
-
12
+
15
13
  def message_id
16
- @message_ids.first
17
- end
18
-
19
- def clean_msg_id( val )
20
- val =~ /.*<(.*)>.*/ ; $1
14
+ message_ids.first
21
15
  end
22
16
 
17
+ private
18
+ def clean_msg_id(val)
19
+ val =~ /.*<(.*)>.*/ ? $1 : val
20
+ end
23
21
  end
24
22
  end
@@ -1,22 +1,15 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/parsers/mime_version_parser'
4
+
2
5
  module Mail
3
6
  class MimeVersionElement
4
-
5
- include Mail::Utilities
6
-
7
- def initialize( string )
8
- mime_version = Mail::Parsers::MimeVersionParser.new.parse(string)
7
+ attr_reader :major, :minor
8
+
9
+ def initialize(string)
10
+ mime_version = Mail::Parsers::MimeVersionParser.parse(string)
9
11
  @major = mime_version.major
10
12
  @minor = mime_version.minor
11
13
  end
12
-
13
- def major
14
- @major
15
- end
16
-
17
- def minor
18
- @minor
19
- end
20
-
21
14
  end
22
15
  end
@@ -1,16 +1,14 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/parsers/phrase_lists_parser'
4
+ require 'mail/utilities'
5
+
2
6
  module Mail
3
7
  class PhraseList
4
-
5
- include Mail::Utilities
6
-
8
+ attr_reader :phrases
9
+
7
10
  def initialize(string)
8
- @phrase_lists = Mail::Parsers::PhraseListsParser.new.parse(string)
11
+ @phrases = Mail::Parsers::PhraseListsParser.parse(string).phrases.map { |p| Mail::Utilities.unquote(p) }
9
12
  end
10
-
11
- def phrases
12
- @phrase_lists.phrases.map { |p| unquote(p) }
13
- end
14
-
15
13
  end
16
14
  end
@@ -1,26 +1,21 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/parsers/received_parser'
4
+ require 'date'
5
+
2
6
  module Mail
3
7
  class ReceivedElement
4
-
5
8
  include Mail::Utilities
6
-
7
- def initialize( string )
8
- received = Mail::Parsers::ReceivedParser.new.parse(string)
9
+ attr_reader :date_time, :info
10
+
11
+ def initialize(string)
12
+ received = Mail::Parsers::ReceivedParser.parse(string)
9
13
  @date_time = ::DateTime.parse("#{received.date} #{received.time}")
10
14
  @info = received.info
11
15
  end
12
-
13
- def date_time
14
- @date_time
15
- end
16
-
17
- def info
18
- @info
19
- end
20
-
16
+
21
17
  def to_s(*args)
22
- "#{@info}; #{@date_time.to_s(*args)}"
18
+ "#{info}; #{date_time.to_s(*args)}"
23
19
  end
24
-
25
20
  end
26
21
  end
data/lib/mail/elements.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Mail
2
3
  register_autoload :Address, 'mail/elements/address'
3
4
  register_autoload :AddressList, 'mail/elements/address_list'
@@ -1,31 +1,22 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'mail/encodings/8bit'
3
4
 
4
5
  module Mail
5
6
  module Encodings
7
+ # 7bit and 8bit are equivalent. 7bit encoding is for text only.
6
8
  class SevenBit < EightBit
7
9
  NAME = '7bit'
8
-
9
10
  PRIORITY = 1
11
+ Encodings.register(NAME, self)
10
12
 
11
- # 7bit and 8bit operate the same
12
-
13
- # Decode the string
14
13
  def self.decode(str)
15
- super
14
+ ::Mail::Utilities.binary_unsafe_to_lf str
16
15
  end
17
-
18
- # Encode the string
16
+
19
17
  def self.encode(str)
20
- super
21
- end
22
-
23
- # Idenity encodings have a fixed cost, 1 byte out per 1 byte in
24
- def self.cost(str)
25
- super
18
+ ::Mail::Utilities.binary_unsafe_to_crlf str
26
19
  end
27
-
28
- Encodings.register(NAME, self)
29
20
  end
30
21
  end
31
22
  end
@@ -1,31 +1,18 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'mail/encodings/binary'
3
4
 
4
5
  module Mail
5
6
  module Encodings
6
7
  class EightBit < Binary
7
8
  NAME = '8bit'
8
-
9
9
  PRIORITY = 4
10
+ Encodings.register(NAME, self)
10
11
 
11
- # 8bit is an identiy encoding, meaning nothing to do
12
-
13
- # Decode the string
14
- def self.decode(str)
15
- str.to_lf
16
- end
17
-
18
- # Encode the string
19
- def self.encode(str)
20
- str.to_crlf
12
+ # Per RFC 2821 4.5.3.1, SMTP lines may not be longer than 1000 octets including the <CRLF>.
13
+ def self.compatible_input?(str)
14
+ !str.lines.find { |line| line.bytesize > 998 }
21
15
  end
22
-
23
- # Idenity encodings have a fixed cost, 1 byte out per 1 byte in
24
- def self.cost(str)
25
- 1.0
26
- end
27
-
28
- Encodings.register(NAME, self)
29
16
  end
30
17
  end
31
18
  end
@@ -1,33 +1,38 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'mail/encodings/7bit'
3
4
 
4
5
  module Mail
5
6
  module Encodings
7
+ # Base64 encoding handles binary content at the cost of 4 output bytes
8
+ # per input byte.
6
9
  class Base64 < SevenBit
7
10
  NAME = 'base64'
8
-
9
11
  PRIORITY = 3
10
-
12
+ Encodings.register(NAME, self)
13
+
11
14
  def self.can_encode?(enc)
12
15
  true
13
16
  end
14
17
 
15
- # Decode the string from Base64
16
18
  def self.decode(str)
17
- RubyVer.decode_base64( str )
19
+ RubyVer.decode_base64(str)
18
20
  end
19
-
20
- # Encode the string to Base64
21
+
21
22
  def self.encode(str)
22
- RubyVer.encode_base64( str ).to_crlf
23
+ ::Mail::Utilities.binary_unsafe_to_crlf(RubyVer.encode_base64(str))
23
24
  end
24
25
 
25
- # Base64 has a fixed cost, 4 bytes out per 3 bytes in
26
+ # 3 bytes in -> 4 bytes out
26
27
  def self.cost(str)
27
- 4.0/3
28
+ 4.0 / 3
28
29
  end
29
30
 
30
- Encodings.register(NAME, self)
31
+ # Ruby Base64 inserts newlines automatically, so it doesn't exceed
32
+ # SMTP line length limits.
33
+ def self.compatible_input?(str)
34
+ true
35
+ end
31
36
  end
32
37
  end
33
38
  end
@@ -1,31 +1,13 @@
1
1
  # encoding: utf-8
2
- require 'mail/encodings/transfer_encoding'
2
+ # frozen_string_literal: true
3
+ require 'mail/encodings/identity'
3
4
 
4
5
  module Mail
5
6
  module Encodings
6
- class Binary < TransferEncoding
7
+ class Binary < Identity
7
8
  NAME = 'binary'
8
-
9
9
  PRIORITY = 5
10
-
11
- # Binary is an identiy encoding, meaning nothing to do
12
-
13
- # Decode the string
14
- def self.decode(str)
15
- str
16
- end
17
-
18
- # Encode the string
19
- def self.encode(str)
20
- str
21
- end
22
-
23
- # Idenity encodings have a fixed cost, 1 byte out per 1 byte in
24
- def self.cost(str)
25
- 1.0
26
- end
27
-
28
- Encodings.register(NAME, self)
10
+ Encodings.register(NAME, self)
29
11
  end
30
12
  end
31
13
  end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/encodings/transfer_encoding'
4
+
5
+ module Mail
6
+ module Encodings
7
+ # Identity encodings do no encoding/decoding and have a fixed cost:
8
+ # 1 byte in -> 1 byte out.
9
+ class Identity < TransferEncoding #:nodoc:
10
+ def self.decode(str)
11
+ str
12
+ end
13
+
14
+ def self.encode(str)
15
+ str
16
+ end
17
+
18
+ # 1 output byte per input byte.
19
+ def self.cost(str)
20
+ 1.0
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,36 +1,42 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'mail/encodings/7bit'
3
4
 
4
5
  module Mail
5
6
  module Encodings
6
7
  class QuotedPrintable < SevenBit
7
8
  NAME='quoted-printable'
8
-
9
+
9
10
  PRIORITY = 2
10
11
 
11
- def self.can_encode?(str)
12
- EightBit.can_encode? str
12
+ def self.can_encode?(enc)
13
+ EightBit.can_encode? enc
13
14
  end
14
15
 
15
16
  # Decode the string from Quoted-Printable. Cope with hard line breaks
16
17
  # that were incorrectly encoded as hex instead of literal CRLF.
17
18
  def self.decode(str)
18
- str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first.to_lf
19
+ str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first
19
20
  end
20
21
 
21
22
  def self.encode(str)
22
- [str.to_lf].pack("M").to_crlf
23
+ [str].pack("M")
23
24
  end
24
25
 
25
26
  def self.cost(str)
26
27
  # These bytes probably do not need encoding
27
28
  c = str.count("\x9\xA\xD\x20-\x3C\x3E-\x7E")
28
- # Everything else turns into =XX where XX is a
29
+ # Everything else turns into =XX where XX is a
29
30
  # two digit hex number (taking 3 bytes)
30
31
  total = (str.bytesize - c)*3 + c
31
32
  total.to_f/str.bytesize
32
33
  end
33
-
34
+
35
+ # QP inserts newlines automatically and cannot violate the SMTP spec.
36
+ def self.compatible_input?(str)
37
+ true
38
+ end
39
+
34
40
  private
35
41
 
36
42
  Encodings.register(NAME, self)
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  module Encodings
4
5
  class TransferEncoding
@@ -6,52 +7,70 @@ module Mail
6
7
 
7
8
  PRIORITY = -1
8
9
 
10
+ # And encoding's superclass can always transport it since the
11
+ # class hierarchy is arranged e.g. Base64 < 7bit < 8bit < Binary.
9
12
  def self.can_transport?(enc)
10
- enc = Encodings.get_name(enc)
11
- if Encodings.defined? enc
12
- Encodings.get_encoding(enc).new.is_a? self
13
- else
14
- false
15
- end
13
+ enc && enc <= self
16
14
  end
17
15
 
16
+ # Override in subclasses to indicate that they can encode text
17
+ # that couldn't be directly transported, e.g. Base64 has 7bit output,
18
+ # but it can encode binary.
18
19
  def self.can_encode?(enc)
19
- can_transport? enc
20
+ can_transport? enc
20
21
  end
21
22
 
22
23
  def self.cost(str)
23
24
  raise "Unimplemented"
24
25
  end
25
26
 
27
+ def self.compatible_input?(str)
28
+ true
29
+ end
30
+
26
31
  def self.to_s
27
32
  self::NAME
28
33
  end
29
34
 
30
- def self.get_best_compatible(source_encoding, str)
31
- if self.can_transport? source_encoding then
32
- source_encoding
35
+ def self.negotiate(message_encoding, source_encoding, str, allowed_encodings = nil)
36
+ message_encoding = Encodings.get_encoding(message_encoding) || Encodings.get_encoding('8bit')
37
+ source_encoding = Encodings.get_encoding(source_encoding)
38
+
39
+ if message_encoding && source_encoding && message_encoding.can_transport?(source_encoding) && source_encoding.compatible_input?(str)
40
+ source_encoding
33
41
  else
34
- choices = []
35
- Encodings.get_all.each do |enc|
36
- choices << enc if self.can_transport? enc and enc.can_encode? source_encoding
37
- end
38
- best = nil
39
- best_cost = 100
40
- choices.each do |enc|
41
- this_cost = enc.cost str
42
- if this_cost < best_cost then
43
- best_cost = this_cost
44
- best = enc
45
- elsif this_cost == best_cost then
46
- best = enc if enc::PRIORITY < best::PRIORITY
47
- end
48
- end
49
- best
42
+ renegotiate(message_encoding, source_encoding, str, allowed_encodings)
50
43
  end
51
44
  end
52
45
 
53
- def to_s
54
- self.class.to_s
46
+ def self.renegotiate(message_encoding, source_encoding, str, allowed_encodings = nil)
47
+ encodings = Encodings.get_all.select do |enc|
48
+ (allowed_encodings.nil? || allowed_encodings.include?(enc)) &&
49
+ message_encoding.can_transport?(enc) &&
50
+ enc.can_encode?(source_encoding)
51
+ end
52
+
53
+ lowest_cost(str, encodings)
54
+ end
55
+
56
+ def self.lowest_cost(str, encodings)
57
+ best = nil
58
+ best_cost = nil
59
+
60
+ encodings.each do |enc|
61
+ # If the current choice cannot be transported safely, give priority
62
+ # to other choices but allow it to be used as a fallback.
63
+ this_cost = enc.cost(str) if enc.compatible_input?(str)
64
+
65
+ if !best_cost || (this_cost && this_cost < best_cost)
66
+ best_cost = this_cost
67
+ best = enc
68
+ elsif this_cost == best_cost
69
+ best = enc if enc::PRIORITY < best::PRIORITY
70
+ end
71
+ end
72
+
73
+ best
55
74
  end
56
75
  end
57
76
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ module Mail
3
+ module Encodings
4
+ class UnixToUnix < TransferEncoding
5
+ NAME = "x-uuencode"
6
+
7
+ def self.decode(str)
8
+ str.sub(/\Abegin \d+ [^\n]*\n/, '').unpack('u').first
9
+ end
10
+
11
+ def self.encode(str)
12
+ [str].pack("u")
13
+ end
14
+
15
+ Encodings.register(NAME, self)
16
+ Encodings.register("uuencode", self)
17
+ Encodings.register("x-uue", self)
18
+ end
19
+ end
20
+ end