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,17 +1,14 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class ContentDispositionElement # :nodoc:
4
5
 
5
6
  include Mail::Utilities
6
7
 
7
8
  def initialize( string )
8
- parser = Mail::ContentDispositionParser.new
9
- if tree = parser.parse(cleaned(string))
10
- @disposition_type = tree.disposition_type.text_value.downcase
11
- @parameters = tree.parameters
12
- else
13
- raise Mail::Field::ParseError.new(ContentDispositionElement, string, parser.failure_reason)
14
- end
9
+ content_disposition = Mail::Parsers::ContentDispositionParser.new.parse(cleaned(string))
10
+ @disposition_type = content_disposition.disposition_type
11
+ @parameters = content_disposition.parameters
15
12
  end
16
13
 
17
14
  def disposition_type
@@ -1,25 +1,22 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class ContentLocationElement # :nodoc:
4
-
5
+
5
6
  include Mail::Utilities
6
-
7
+
7
8
  def initialize( string )
8
- parser = Mail::ContentLocationParser.new
9
- if tree = parser.parse(string)
10
- @location = tree.location.text_value
11
- else
12
- raise Mail::Field::ParseError.new(ContentLocationElement, string, parser.failure_reason)
13
- end
9
+ content_location = Mail::Parsers::ContentLocationParser.new.parse(string)
10
+ @location = content_location.location
14
11
  end
15
-
12
+
16
13
  def location
17
14
  @location
18
15
  end
19
-
16
+
20
17
  def to_s(*args)
21
18
  location.to_s
22
19
  end
23
-
20
+
24
21
  end
25
22
  end
@@ -1,19 +1,13 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class ContentTransferEncodingElement
4
5
 
5
6
  include Mail::Utilities
6
7
 
7
- def initialize( string )
8
- parser = Mail::ContentTransferEncodingParser.new
9
- case
10
- when string.blank?
11
- @encoding = ''
12
- when tree = parser.parse(string.to_s.downcase)
13
- @encoding = tree.encoding.text_value
14
- else
15
- raise Mail::Field::ParseError.new(ContentTransferEncodingElement, string, parser.failure_reason)
16
- end
8
+ def initialize(string)
9
+ content_transfer_encoding = Mail::Parsers::ContentTransferEncodingParser.new.parse(string)
10
+ @encoding = content_transfer_encoding.encoding
17
11
  end
18
12
 
19
13
  def encoding
@@ -1,18 +1,15 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class ContentTypeElement # :nodoc:
4
5
 
5
6
  include Mail::Utilities
6
7
 
7
8
  def initialize( string )
8
- parser = Mail::ContentTypeParser.new
9
- if tree = parser.parse(cleaned(string))
10
- @main_type = tree.main_type.text_value.downcase
11
- @sub_type = tree.sub_type.text_value.downcase
12
- @parameters = tree.parameters
13
- else
14
- raise Mail::Field::ParseError.new(ContentTypeElement, string, parser.failure_reason)
15
- end
9
+ content_type = Mail::Parsers::ContentTypeParser.new.parse(cleaned(string))
10
+ @main_type = content_type.main_type
11
+ @sub_type = content_type.sub_type
12
+ @parameters = content_type.parameters
16
13
  end
17
14
 
18
15
  def main_type
@@ -1,17 +1,14 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class DateTimeElement # :nodoc:
4
5
 
5
6
  include Mail::Utilities
6
7
 
7
8
  def initialize( string )
8
- parser = Mail::DateTimeParser.new
9
- if tree = parser.parse(string)
10
- @date_string = tree.date.text_value
11
- @time_string = tree.time.text_value
12
- else
13
- raise Mail::Field::ParseError.new(DateTimeElement, string, parser.failure_reason)
14
- end
9
+ date_time = Mail::Parsers::DateTimeParser.new.parse(string)
10
+ @date_string = date_time.date_string
11
+ @time_string = date_time.time_string
15
12
  end
16
13
 
17
14
  def date_string
@@ -1,21 +1,14 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class EnvelopeFromElement
4
5
 
5
6
  include Mail::Utilities
6
7
 
7
8
  def initialize( string )
8
- parser = Mail::EnvelopeFromParser.new
9
- if @tree = parser.parse(string)
10
- @address = tree.addr_spec.text_value.strip
11
- @date_time = ::DateTime.parse("#{tree.ctime_date.text_value}")
12
- else
13
- raise Mail::Field::ParseError.new(EnvelopeFromElement, string, parser.failure_reason)
14
- end
15
- end
16
-
17
- def tree
18
- @tree
9
+ @envelope_from = Mail::Parsers::EnvelopeFromParser.new.parse(string)
10
+ @address = @envelope_from.address
11
+ @date_time = ::DateTime.parse(@envelope_from.ctime_date)
19
12
  end
20
13
 
21
14
  def date_time
@@ -26,8 +19,21 @@ module Mail
26
19
  @address
27
20
  end
28
21
 
29
- def to_s(*args)
30
- "#{@info}; #{@date_time.to_s(*args)}"
22
+ # RFC 4155:
23
+ # a timestamp indicating the UTC date and time when the message
24
+ # was originally received, conformant with the syntax of the
25
+ # traditional UNIX 'ctime' output sans timezone (note that the
26
+ # use of UTC precludes the need for a timezone indicator);
27
+ def formatted_date_time
28
+ if @date_time.respond_to?(:ctime)
29
+ @date_time.ctime
30
+ else
31
+ @date_time.strftime '%a %b %e %T %Y'
32
+ end
33
+ end
34
+
35
+ def to_s
36
+ "#{@address} #{formatted_date_time}"
31
37
  end
32
38
 
33
39
  end
@@ -1,26 +1,23 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class MessageIdsElement
4
-
5
+
5
6
  include Mail::Utilities
6
-
7
+
7
8
  def initialize(string)
8
- parser = Mail::MessageIdsParser.new
9
- if tree = parser.parse(string)
10
- @message_ids = tree.message_ids.map { |msg_id| clean_msg_id(msg_id.text_value) }
11
- else
12
- raise Mail::Field::ParseError.new(MessageIdsElement, string, parser.failure_reason)
13
- end
9
+ raise Mail::Field::ParseError.new(Mail::MessageIdsElement, string, 'nil is invalid') if string.nil?
10
+ @message_ids = Mail::Parsers::MessageIdsParser.new.parse(string).message_ids.map { |msg_id| clean_msg_id(msg_id) }
14
11
  end
15
-
12
+
16
13
  def message_ids
17
14
  @message_ids
18
15
  end
19
-
16
+
20
17
  def message_id
21
18
  @message_ids.first
22
19
  end
23
-
20
+
24
21
  def clean_msg_id( val )
25
22
  val =~ /.*<(.*)>.*/ ; $1
26
23
  end
@@ -1,17 +1,14 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class MimeVersionElement
4
5
 
5
6
  include Mail::Utilities
6
7
 
7
8
  def initialize( string )
8
- parser = Mail::MimeVersionParser.new
9
- if tree = parser.parse(string)
10
- @major = tree.major.text_value
11
- @minor = tree.minor.text_value
12
- else
13
- raise Mail::Field::ParseError.new(MimeVersionElement, string, parser.failure_reason)
14
- end
9
+ mime_version = Mail::Parsers::MimeVersionParser.new.parse(string)
10
+ @major = mime_version.major
11
+ @minor = mime_version.minor
15
12
  end
16
13
 
17
14
  def major
@@ -1,20 +1,16 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class PhraseList
4
5
 
5
6
  include Mail::Utilities
6
7
 
7
8
  def initialize(string)
8
- parser = Mail::PhraseListsParser.new
9
- if tree = parser.parse(string)
10
- @phrases = tree.phrases
11
- else
12
- raise Mail::Field::ParseError.new(PhraseList, string, parser.failure_reason)
13
- end
9
+ @phrase_lists = Mail::Parsers::PhraseListsParser.new.parse(string)
14
10
  end
15
11
 
16
12
  def phrases
17
- @phrases.map { |p| unquote(p.text_value) }
13
+ @phrase_lists.phrases.map { |p| unquote(p) }
18
14
  end
19
15
 
20
16
  end
@@ -1,17 +1,14 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  module Mail
3
4
  class ReceivedElement
4
5
 
5
6
  include Mail::Utilities
6
7
 
7
8
  def initialize( string )
8
- parser = Mail::ReceivedParser.new
9
- if tree = parser.parse(string)
10
- @date_time = ::DateTime.parse("#{tree.date_time.date.text_value} #{tree.date_time.time.text_value}")
11
- @info = tree.name_val_list.text_value
12
- else
13
- raise Mail::Field::ParseError.new(ReceivedElement, string, parser.failure_reason)
14
- end
9
+ received = Mail::Parsers::ReceivedParser.new.parse(string)
10
+ @date_time = ::DateTime.parse("#{received.date} #{received.time}")
11
+ @info = received.info
15
12
  end
16
13
 
17
14
  def date_time
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,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'mail/encodings/8bit'
3
4
 
4
5
  module Mail
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'mail/encodings/binary'
3
4
 
4
5
  module Mail
@@ -12,12 +13,12 @@ module Mail
12
13
 
13
14
  # Decode the string
14
15
  def self.decode(str)
15
- str.to_lf
16
+ ::Mail::Utilities.to_lf str
16
17
  end
17
18
 
18
19
  # Encode the string
19
20
  def self.encode(str)
20
- str.to_crlf
21
+ ::Mail::Utilities.to_crlf str
21
22
  end
22
23
 
23
24
  # Idenity encodings have a fixed cost, 1 byte out per 1 byte in
@@ -25,6 +26,11 @@ module Mail
25
26
  1.0
26
27
  end
27
28
 
29
+ # Per RFC 2821 4.5.3.1, SMTP lines may not be longer than 1000 octets including the <CRLF>.
30
+ def self.compatible_input?(str)
31
+ !str.lines.find { |line| line.length > 998 }
32
+ end
33
+
28
34
  Encodings.register(NAME, self)
29
35
  end
30
36
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'mail/encodings/7bit'
3
4
 
4
5
  module Mail
@@ -19,7 +20,7 @@ module Mail
19
20
 
20
21
  # Encode the string to Base64
21
22
  def self.encode(str)
22
- RubyVer.encode_base64( str ).to_crlf
23
+ ::Mail::Utilities.to_crlf(RubyVer.encode_base64( str ))
23
24
  end
24
25
 
25
26
  # Base64 has a fixed cost, 4 bytes out per 3 bytes in
@@ -27,6 +28,11 @@ module Mail
27
28
  4.0/3
28
29
  end
29
30
 
31
+ # Base64 inserts newlines automatically and cannot violate the SMTP spec.
32
+ def self.compatible_input?(str)
33
+ true
34
+ end
35
+
30
36
  Encodings.register(NAME, self)
31
37
  end
32
38
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'mail/encodings/transfer_encoding'
3
4
 
4
5
  module Mail
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  require 'mail/encodings/7bit'
3
4
 
4
5
  module Mail
@@ -12,13 +13,14 @@ module Mail
12
13
  EightBit.can_encode? str
13
14
  end
14
15
 
15
- # Decode the string from Quoted-Printable
16
+ # Decode the string from Quoted-Printable. Cope with hard line breaks
17
+ # that were incorrectly encoded as hex instead of literal CRLF.
16
18
  def self.decode(str)
17
- str.unpack("M*").first
19
+ ::Mail::Utilities.to_lf str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first
18
20
  end
19
21
 
20
22
  def self.encode(str)
21
- [str].pack("M").to_crlf
23
+ ::Mail::Utilities.to_crlf([::Mail::Utilities.to_lf(str)].pack("M"))
22
24
  end
23
25
 
24
26
  def self.cost(str)
@@ -29,7 +31,12 @@ module Mail
29
31
  total = (str.bytesize - c)*3 + c
30
32
  total.to_f/str.bytesize
31
33
  end
32
-
34
+
35
+ # QP inserts newlines automatically and cannot violate the SMTP spec.
36
+ def self.compatible_input?(str)
37
+ true
38
+ end
39
+
33
40
  private
34
41
 
35
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
@@ -23,30 +24,39 @@ module Mail
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
35
  def self.get_best_compatible(source_encoding, str)
31
- if self.can_transport? source_encoding then
32
- source_encoding
36
+ if self.can_transport?(source_encoding) && self.compatible_input?(str)
37
+ source_encoding
33
38
  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
39
+ choices = Encodings.get_all.select do |enc|
40
+ self.can_transport?(enc) && enc.can_encode?(source_encoding)
41
+ end
42
+
43
+ best = nil
44
+ best_cost = nil
45
+
46
+ choices.each do |enc|
47
+ # If the current choice cannot be transported safely,
48
+ # give priority to other choices but allow it to be used as a fallback.
49
+ this_cost = enc.cost(str) if enc.compatible_input?(str)
50
+
51
+ if !best_cost || (this_cost && this_cost < best_cost)
52
+ best_cost = this_cost
53
+ best = enc
54
+ elsif this_cost == best_cost
55
+ best = enc if enc::PRIORITY < best::PRIORITY
48
56
  end
49
- best
57
+ end
58
+
59
+ best
50
60
  end
51
61
  end
52
62
 
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module Mail
3
+ module Encodings
4
+ module UnixToUnix
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
+ end
17
+ end
18
+ end
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Mail
4
5
  # Raised when attempting to decode an unknown encoding type
@@ -7,7 +8,7 @@ module Mail
7
8
 
8
9
  module Encodings
9
10
 
10
- include Mail::Patterns
11
+ include Mail::Constants
11
12
  extend Mail::Utilities
12
13
 
13
14
  @transfer_encodings = {}
@@ -47,13 +48,21 @@ module Mail
47
48
  end
48
49
 
49
50
  def Encodings.get_name(enc)
50
- enc = enc.to_s.gsub("-", "_").downcase
51
+ underscoreize(enc).downcase
52
+ end
53
+
54
+ def Encodings.transcode_charset(str, from_charset, to_charset = 'UTF-8')
55
+ if from_charset
56
+ RubyVer.transcode_charset str, from_charset, to_charset
57
+ else
58
+ str
59
+ end
51
60
  end
52
61
 
53
62
  # Encodes a parameter value using URI Escaping, note the language field 'en' can
54
63
  # be set using Mail::Configuration, like so:
55
64
  #
56
- # Mail.defaults.do
65
+ # Mail.defaults do
57
66
  # param_encode_language 'jp'
58
67
  # end
59
68
  #
@@ -114,32 +123,16 @@ module Mail
114
123
  # String has to be of the format =?<encoding>?[QB]?<string>?=
115
124
  def Encodings.value_decode(str)
116
125
  # Optimization: If there's no encoded-words in the string, just return it
117
- return str unless str.index("=?")
126
+ return str unless str =~ ENCODED_VALUE
118
127
 
119
- str = str.gsub(/\?=(\s*)=\?/, '?==?') # Remove whitespaces between 'encoded-word's
128
+ lines = collapse_adjacent_encodings(str)
120
129
 
121
130
  # Split on white-space boundaries with capture, so we capture the white-space as well
122
- str.split(/([ \t])/).map do |text|
123
- if text.index('=?') .nil?
124
- text
125
- else
126
- # Join QP encoded-words that are adjacent to avoid decoding partial chars
127
- text.gsub!(/\?\=\=\?.+?\?[Qq]\?/m, '') if text =~ /\?==\?/
128
-
129
- # Search for occurences of quoted strings or plain strings
130
- text.scan(/( # Group around entire regex to include it in matches
131
- \=\?[^?]+\?([QB])\?[^?]+?\?\= # Quoted String with subgroup for encoding method
132
- | # or
133
- .+?(?=\=\?|$) # Plain String
134
- )/xmi).map do |matches|
135
- string, method = *matches
136
- if method == 'b' || method == 'B'
137
- b_value_decode(string)
138
- elsif method == 'q' || method == 'Q'
139
- q_value_decode(string)
140
- else
141
- string
142
- end
131
+ lines.each do |line|
132
+ line.gsub!(ENCODED_VALUE) do |string|
133
+ case $2
134
+ when *B_VALUES then b_value_decode(string)
135
+ when *Q_VALUES then q_value_decode(string)
143
136
  end
144
137
  end
145
138
  end.join("")
@@ -148,7 +141,7 @@ module Mail
148
141
  # Takes an encoded string of the format =?<encoding>?[QB]?<string>?=
149
142
  def Encodings.unquote_and_convert_to(str, to_encoding)
150
143
  output = value_decode( str ).to_s # output is already converted to UTF-8
151
-
144
+
152
145
  if 'utf8' == to_encoding.to_s.downcase.gsub("-", "")
153
146
  output
154
147
  elsif to_encoding
@@ -157,7 +150,7 @@ module Mail
157
150
  output.encode(to_encoding)
158
151
  else
159
152
  require 'iconv'
160
- Iconv.iconv(to_encoding, 'UTF-8', output).first
153
+ Iconv.iconv(to_encoding, 'UTF-8', output).first
161
154
  end
162
155
  rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
163
156
  # the 'from' parameter specifies a charset other than what the text
@@ -194,8 +187,8 @@ module Mail
194
187
  if word.ascii_only?
195
188
  word
196
189
  else
197
- previous_non_ascii = tokens[i-1] && !tokens[i-1].ascii_only?
198
- if previous_non_ascii
190
+ previous_non_ascii = i>0 && tokens[i-1] && !tokens[i-1].ascii_only?
191
+ if previous_non_ascii #why are we adding an extra space here?
199
192
  word = " #{word}"
200
193
  end
201
194
  Encodings.b_value_encode(word, charset)
@@ -256,17 +249,39 @@ module Mail
256
249
  RubyVer.q_value_decode(str)
257
250
  end
258
251
 
259
- def Encodings.split_encoding_from_string( str )
260
- match = str.match(/\=\?([^?]+)?\?[QB]\?(.+)?\?\=/mi)
261
- if match
262
- match[1]
263
- else
264
- nil
265
- end
266
- end
267
-
268
252
  def Encodings.find_encoding(str)
269
253
  RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
270
254
  end
255
+
256
+ # Gets the encoding type (Q or B) from the string.
257
+ def Encodings.value_encoding_from_string(str)
258
+ str[ENCODED_VALUE, 1]
259
+ end
260
+
261
+ # When the encoded string consists of multiple lines, lines with the same
262
+ # encoding (Q or B) can be joined together.
263
+ #
264
+ # String has to be of the format =?<encoding>?[QB]?<string>?=
265
+ def Encodings.collapse_adjacent_encodings(str)
266
+ results = []
267
+ previous_encoding = nil
268
+ lines = str.split(FULL_ENCODED_VALUE)
269
+ lines.each_slice(2) do |unencoded, encoded|
270
+ if encoded
271
+ encoding = value_encoding_from_string(encoded)
272
+ if encoding == previous_encoding && Utilities.blank?(unencoded)
273
+ results.last << encoded
274
+ else
275
+ results << unencoded unless unencoded == EMPTY
276
+ results << encoded
277
+ end
278
+ previous_encoding = encoding
279
+ else
280
+ results << unencoded
281
+ end
282
+ end
283
+
284
+ results
285
+ end
271
286
  end
272
287
  end
data/lib/mail/envelope.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # = Mail Envelope
4
5
  #
@@ -14,11 +15,6 @@ module Mail
14
15
  super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
15
16
  end
16
17
 
17
- def tree
18
- @element ||= Mail::EnvelopeFromElement.new(value)
19
- @tree ||= @element.tree
20
- end
21
-
22
18
  def element
23
19
  @element ||= Mail::EnvelopeFromElement.new(value)
24
20
  end