mail 2.6.1 → 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
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