mail 2.6.1 → 2.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +150 -107
  4. data/lib/mail/attachments_list.rb +13 -10
  5. data/lib/mail/body.rb +104 -90
  6. data/lib/mail/check_delivery_params.rb +55 -10
  7. data/lib/mail/configuration.rb +3 -0
  8. data/lib/mail/constants.rb +79 -0
  9. data/lib/mail/elements/address.rb +96 -108
  10. data/lib/mail/elements/address_list.rb +13 -30
  11. data/lib/mail/elements/content_disposition_element.rb +10 -16
  12. data/lib/mail/elements/content_location_element.rb +9 -13
  13. data/lib/mail/elements/content_transfer_encoding_element.rb +7 -11
  14. data/lib/mail/elements/content_type_element.rb +17 -23
  15. data/lib/mail/elements/date_time_element.rb +8 -15
  16. data/lib/mail/elements/envelope_from_element.rb +23 -23
  17. data/lib/mail/elements/message_ids_element.rb +22 -15
  18. data/lib/mail/elements/mime_version_element.rb +8 -15
  19. data/lib/mail/elements/phrase_list.rb +13 -10
  20. data/lib/mail/elements/received_element.rb +28 -19
  21. data/lib/mail/elements.rb +1 -0
  22. data/lib/mail/encodings/7bit.rb +10 -14
  23. data/lib/mail/encodings/8bit.rb +5 -18
  24. data/lib/mail/encodings/base64.rb +15 -10
  25. data/lib/mail/encodings/binary.rb +4 -22
  26. data/lib/mail/encodings/identity.rb +24 -0
  27. data/lib/mail/encodings/quoted_printable.rb +13 -7
  28. data/lib/mail/encodings/transfer_encoding.rb +47 -28
  29. data/lib/mail/encodings/unix_to_unix.rb +20 -0
  30. data/lib/mail/encodings.rb +102 -92
  31. data/lib/mail/envelope.rb +12 -14
  32. data/lib/mail/field.rb +121 -85
  33. data/lib/mail/field_list.rb +62 -8
  34. data/lib/mail/fields/bcc_field.rb +42 -48
  35. data/lib/mail/fields/cc_field.rb +29 -50
  36. data/lib/mail/fields/comments_field.rb +28 -37
  37. data/lib/mail/fields/common_address_field.rb +170 -0
  38. data/lib/mail/fields/common_date_field.rb +58 -0
  39. data/lib/mail/fields/common_field.rb +77 -0
  40. data/lib/mail/fields/common_message_id_field.rb +42 -0
  41. data/lib/mail/fields/content_description_field.rb +8 -14
  42. data/lib/mail/fields/content_disposition_field.rb +20 -44
  43. data/lib/mail/fields/content_id_field.rb +25 -51
  44. data/lib/mail/fields/content_location_field.rb +12 -25
  45. data/lib/mail/fields/content_transfer_encoding_field.rb +32 -31
  46. data/lib/mail/fields/content_type_field.rb +51 -80
  47. data/lib/mail/fields/date_field.rb +24 -52
  48. data/lib/mail/fields/from_field.rb +29 -50
  49. data/lib/mail/fields/in_reply_to_field.rb +39 -49
  50. data/lib/mail/fields/keywords_field.rb +19 -32
  51. data/lib/mail/fields/message_id_field.rb +26 -71
  52. data/lib/mail/fields/mime_version_field.rb +20 -30
  53. data/lib/mail/fields/named_structured_field.rb +11 -0
  54. data/lib/mail/fields/named_unstructured_field.rb +11 -0
  55. data/lib/mail/fields/optional_field.rb +10 -7
  56. data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +16 -13
  57. data/lib/mail/fields/received_field.rb +44 -57
  58. data/lib/mail/fields/references_field.rb +36 -49
  59. data/lib/mail/fields/reply_to_field.rb +29 -50
  60. data/lib/mail/fields/resent_bcc_field.rb +29 -50
  61. data/lib/mail/fields/resent_cc_field.rb +29 -50
  62. data/lib/mail/fields/resent_date_field.rb +6 -30
  63. data/lib/mail/fields/resent_from_field.rb +29 -50
  64. data/lib/mail/fields/resent_message_id_field.rb +6 -29
  65. data/lib/mail/fields/resent_sender_field.rb +28 -57
  66. data/lib/mail/fields/resent_to_field.rb +29 -50
  67. data/lib/mail/fields/return_path_field.rb +51 -55
  68. data/lib/mail/fields/sender_field.rb +35 -56
  69. data/lib/mail/fields/structured_field.rb +4 -30
  70. data/lib/mail/fields/subject_field.rb +10 -11
  71. data/lib/mail/fields/to_field.rb +29 -50
  72. data/lib/mail/fields/unstructured_field.rb +36 -50
  73. data/lib/mail/fields.rb +1 -0
  74. data/lib/mail/header.rb +73 -110
  75. data/lib/mail/indifferent_hash.rb +1 -0
  76. data/lib/mail/mail.rb +6 -11
  77. data/lib/mail/matchers/attachment_matchers.rb +44 -0
  78. data/lib/mail/matchers/has_sent_mail.rb +53 -9
  79. data/lib/mail/message.rb +132 -136
  80. data/lib/mail/multibyte/chars.rb +24 -180
  81. data/lib/mail/multibyte/unicode.rb +31 -26
  82. data/lib/mail/multibyte/utils.rb +27 -43
  83. data/lib/mail/multibyte.rb +56 -16
  84. data/lib/mail/network/delivery_methods/exim.rb +9 -11
  85. data/lib/mail/network/delivery_methods/file_delivery.rb +14 -16
  86. data/lib/mail/network/delivery_methods/logger_delivery.rb +34 -0
  87. data/lib/mail/network/delivery_methods/sendmail.rb +68 -24
  88. data/lib/mail/network/delivery_methods/smtp.rb +77 -54
  89. data/lib/mail/network/delivery_methods/smtp_connection.rb +5 -9
  90. data/lib/mail/network/delivery_methods/test_mailer.rb +9 -9
  91. data/lib/mail/network/retriever_methods/base.rb +9 -8
  92. data/lib/mail/network/retriever_methods/imap.rb +21 -7
  93. data/lib/mail/network/retriever_methods/pop3.rb +6 -3
  94. data/lib/mail/network/retriever_methods/test_retriever.rb +4 -2
  95. data/lib/mail/network.rb +2 -0
  96. data/lib/mail/parser_tools.rb +15 -0
  97. data/lib/mail/parsers/address_lists_parser.rb +33226 -116
  98. data/lib/mail/parsers/address_lists_parser.rl +179 -0
  99. data/lib/mail/parsers/content_disposition_parser.rb +883 -49
  100. data/lib/mail/parsers/content_disposition_parser.rl +89 -0
  101. data/lib/mail/parsers/content_location_parser.rb +810 -23
  102. data/lib/mail/parsers/content_location_parser.rl +78 -0
  103. data/lib/mail/parsers/content_transfer_encoding_parser.rb +510 -21
  104. data/lib/mail/parsers/content_transfer_encoding_parser.rl +71 -0
  105. data/lib/mail/parsers/content_type_parser.rb +1031 -47
  106. data/lib/mail/parsers/content_type_parser.rl +90 -0
  107. data/lib/mail/parsers/date_time_parser.rb +879 -24
  108. data/lib/mail/parsers/date_time_parser.rl +69 -0
  109. data/lib/mail/parsers/envelope_from_parser.rb +3670 -40
  110. data/lib/mail/parsers/envelope_from_parser.rl +89 -0
  111. data/lib/mail/parsers/message_ids_parser.rb +5147 -25
  112. data/lib/mail/parsers/message_ids_parser.rl +93 -0
  113. data/lib/mail/parsers/mime_version_parser.rb +498 -26
  114. data/lib/mail/parsers/mime_version_parser.rl +68 -0
  115. data/lib/mail/parsers/phrase_lists_parser.rb +872 -21
  116. data/lib/mail/parsers/phrase_lists_parser.rl +90 -0
  117. data/lib/mail/parsers/received_parser.rb +8777 -42
  118. data/lib/mail/parsers/received_parser.rl +91 -0
  119. data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
  120. data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
  121. data/lib/mail/parsers/rfc2045_mime.rl +16 -0
  122. data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
  123. data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
  124. data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
  125. data/lib/mail/parsers/rfc5322.rl +74 -0
  126. data/lib/mail/parsers/rfc5322_address.rl +72 -0
  127. data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
  128. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  129. data/lib/mail/parsers.rb +12 -25
  130. data/lib/mail/part.rb +11 -12
  131. data/lib/mail/parts_list.rb +88 -14
  132. data/lib/mail/smtp_envelope.rb +57 -0
  133. data/lib/mail/utilities.rb +377 -40
  134. data/lib/mail/values/unicode_tables.dat +0 -0
  135. data/lib/mail/version.rb +8 -15
  136. data/lib/mail/yaml.rb +30 -0
  137. data/lib/mail.rb +9 -32
  138. metadata +138 -94
  139. data/CHANGELOG.rdoc +0 -752
  140. data/CONTRIBUTING.md +0 -60
  141. data/Dependencies.txt +0 -2
  142. data/Gemfile +0 -15
  143. data/Rakefile +0 -29
  144. data/TODO.rdoc +0 -9
  145. data/VERSION +0 -4
  146. data/lib/mail/core_extensions/nil.rb +0 -19
  147. data/lib/mail/core_extensions/object.rb +0 -13
  148. data/lib/mail/core_extensions/smtp.rb +0 -24
  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/core_extensions/string.rb +0 -43
  152. data/lib/mail/fields/common/address_container.rb +0 -16
  153. data/lib/mail/fields/common/common_address.rb +0 -135
  154. data/lib/mail/fields/common/common_date.rb +0 -35
  155. data/lib/mail/fields/common/common_field.rb +0 -57
  156. data/lib/mail/fields/common/common_message_id.rb +0 -48
  157. data/lib/mail/multibyte/exceptions.rb +0 -8
  158. data/lib/mail/parsers/ragel/common.rl +0 -184
  159. data/lib/mail/parsers/ragel/parser_info.rb +0 -61
  160. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
  161. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
  162. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
  163. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
  164. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
  165. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
  166. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
  167. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
  168. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
  169. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
  170. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
  171. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
  172. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2129
  173. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
  174. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
  175. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
  176. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
  177. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
  178. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
  179. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
  180. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
  181. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
  182. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
  183. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
  184. data/lib/mail/parsers/ragel/ruby.rb +0 -39
  185. data/lib/mail/parsers/ragel.rb +0 -17
  186. data/lib/mail/patterns.rb +0 -37
  187. data/lib/mail/version_specific/ruby_1_8.rb +0 -119
  188. data/lib/mail/version_specific/ruby_1_9.rb +0 -159
@@ -1,24 +1,31 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/parsers/message_ids_parser'
4
+ require 'mail/utilities'
5
+
2
6
  module Mail
3
- class MessageIdsElement
4
-
5
- include Mail::Utilities
6
-
7
- def initialize(string)
8
- @message_ids = Mail::Parsers::MessageIdsParser.new.parse(string).message_ids.map { |msg_id| clean_msg_id(msg_id) }
7
+ class MessageIdsElement #:nodoc:
8
+ def self.parse(string)
9
+ new(string).tap(&:message_ids)
9
10
  end
10
-
11
- def message_ids
12
- @message_ids
11
+
12
+ attr_reader :message_ids
13
+
14
+ def initialize(string)
15
+ @message_ids = parse(string)
13
16
  end
14
-
17
+
15
18
  def message_id
16
- @message_ids.first
17
- end
18
-
19
- def clean_msg_id( val )
20
- val =~ /.*<(.*)>.*/ ; $1
19
+ message_ids.first
21
20
  end
22
21
 
22
+ private
23
+ def parse(string)
24
+ if Utilities.blank? string
25
+ []
26
+ else
27
+ Mail::Parsers::MessageIdsParser.parse(string).message_ids
28
+ end
29
+ end
23
30
  end
24
31
  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
- class MimeVersionElement
4
-
5
- include Mail::Utilities
6
-
7
- def initialize( string )
8
- mime_version = Mail::Parsers::MimeVersionParser.new.parse(string)
6
+ class MimeVersionElement #:nodoc:
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,19 @@
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
- class PhraseList
4
-
5
- include Mail::Utilities
6
-
7
+ class PhraseList #:nodoc:
8
+ attr_reader :phrases
9
+
7
10
  def initialize(string)
8
- @phrase_lists = Mail::Parsers::PhraseListsParser.new.parse(string)
11
+ @phrases =
12
+ if Utilities.blank? string
13
+ []
14
+ else
15
+ Mail::Parsers::PhraseListsParser.parse(string).phrases.map { |p| Mail::Utilities.unquote(p) }
16
+ end
9
17
  end
10
-
11
- def phrases
12
- @phrase_lists.phrases.map { |p| unquote(p) }
13
- end
14
-
15
18
  end
16
19
  end
@@ -1,26 +1,35 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'mail/parsers/received_parser'
4
+ require 'mail/utilities'
5
+ require 'date'
6
+
2
7
  module Mail
3
- class ReceivedElement
4
-
5
- include Mail::Utilities
6
-
7
- def initialize( string )
8
- received = Mail::Parsers::ReceivedParser.new.parse(string)
9
- @date_time = ::DateTime.parse("#{received.date} #{received.time}")
10
- @info = received.info
8
+ class ReceivedElement #:nodoc:
9
+ attr_reader :info, :date_time
10
+
11
+ def initialize(string)
12
+ if Utilities.blank? string
13
+ @date_time = nil
14
+ @info = nil
15
+ else
16
+ received = Mail::Parsers::ReceivedParser.parse(string)
17
+ @date_time = datetime_for(received)
18
+ @info = received.info
19
+ end
11
20
  end
12
-
13
- def date_time
14
- @date_time
15
- end
16
-
17
- def info
18
- @info
19
- end
20
-
21
+
21
22
  def to_s(*args)
22
- "#{@info}; #{@date_time.to_s(*args)}"
23
+ "#{info}; #{date_time.to_s(*args)}"
23
24
  end
24
-
25
+
26
+ private
27
+ def datetime_for(received)
28
+ ::DateTime.parse("#{received.date} #{received.time}")
29
+ rescue ArgumentError => e
30
+ raise e unless e.message == 'invalid date'
31
+ warn "WARNING: Invalid date field for received element (#{received.date} #{received.time}): #{e.class}: #{e.message}"
32
+ nil
33
+ end
25
34
  end
26
35
  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,27 @@
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
20
 
28
- Encodings.register(NAME, self)
21
+ # Per RFC 2045 2.7. 7bit Data, No octets with decimal values greater than 127 are allowed.
22
+ def self.compatible_input?(str)
23
+ str.ascii_only? && super
24
+ end
29
25
  end
30
26
  end
31
27
  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
+ Utilities.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(Utilities.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
+ ::Mail::Utilities.to_lf ::Mail::Utilities.to_crlf(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
+ ::Mail::Utilities.to_crlf [::Mail::Utilities.to_lf(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