mail 2.5.5 → 2.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +170 -108
  4. data/lib/mail/attachments_list.rb +13 -10
  5. data/lib/mail/body.rb +105 -91
  6. data/lib/mail/check_delivery_params.rb +30 -22
  7. data/lib/mail/configuration.rb +3 -0
  8. data/lib/mail/constants.rb +79 -0
  9. data/lib/mail/elements/address.rb +118 -174
  10. data/lib/mail/elements/address_list.rb +16 -56
  11. data/lib/mail/elements/content_disposition_element.rb +12 -22
  12. data/lib/mail/elements/content_location_element.rb +9 -17
  13. data/lib/mail/elements/content_transfer_encoding_element.rb +8 -19
  14. data/lib/mail/elements/content_type_element.rb +20 -30
  15. data/lib/mail/elements/date_time_element.rb +10 -21
  16. data/lib/mail/elements/envelope_from_element.rb +23 -31
  17. data/lib/mail/elements/message_ids_element.rb +22 -20
  18. data/lib/mail/elements/mime_version_element.rb +10 -21
  19. data/lib/mail/elements/phrase_list.rb +13 -15
  20. data/lib/mail/elements/received_element.rb +26 -21
  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 -93
  31. data/lib/mail/envelope.rb +12 -19
  32. data/lib/mail/field.rb +143 -71
  33. data/lib/mail/field_list.rb +73 -19
  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 +31 -36
  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 +43 -51
  73. data/lib/mail/fields.rb +1 -0
  74. data/lib/mail/header.rb +78 -129
  75. data/lib/mail/indifferent_hash.rb +1 -0
  76. data/lib/mail/mail.rb +18 -11
  77. data/lib/mail/matchers/attachment_matchers.rb +44 -0
  78. data/lib/mail/matchers/has_sent_mail.rb +81 -4
  79. data/lib/mail/message.rb +142 -139
  80. data/lib/mail/multibyte/chars.rb +24 -180
  81. data/lib/mail/multibyte/unicode.rb +32 -27
  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 +6 -4
  85. data/lib/mail/network/delivery_methods/file_delivery.rb +12 -10
  86. data/lib/mail/network/delivery_methods/logger_delivery.rb +34 -0
  87. data/lib/mail/network/delivery_methods/sendmail.rb +63 -21
  88. data/lib/mail/network/delivery_methods/smtp.rb +76 -50
  89. data/lib/mail/network/delivery_methods/smtp_connection.rb +4 -4
  90. data/lib/mail/network/delivery_methods/test_mailer.rb +5 -2
  91. data/lib/mail/network/retriever_methods/base.rb +9 -8
  92. data/lib/mail/network/retriever_methods/imap.rb +37 -18
  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 +33242 -0
  98. data/lib/mail/parsers/address_lists_parser.rl +179 -0
  99. data/lib/mail/parsers/content_disposition_parser.rb +901 -0
  100. data/lib/mail/parsers/content_disposition_parser.rl +89 -0
  101. data/lib/mail/parsers/content_location_parser.rb +822 -0
  102. data/lib/mail/parsers/content_location_parser.rl +78 -0
  103. data/lib/mail/parsers/content_transfer_encoding_parser.rb +522 -0
  104. data/lib/mail/parsers/content_transfer_encoding_parser.rl +71 -0
  105. data/lib/mail/parsers/content_type_parser.rb +1048 -0
  106. data/lib/mail/parsers/content_type_parser.rl +90 -0
  107. data/lib/mail/parsers/date_time_parser.rb +891 -0
  108. data/lib/mail/parsers/date_time_parser.rl +69 -0
  109. data/lib/mail/parsers/envelope_from_parser.rb +3675 -0
  110. data/lib/mail/parsers/envelope_from_parser.rl +89 -0
  111. data/lib/mail/parsers/message_ids_parser.rb +5161 -0
  112. data/lib/mail/parsers/message_ids_parser.rl +93 -0
  113. data/lib/mail/parsers/mime_version_parser.rb +513 -0
  114. data/lib/mail/parsers/mime_version_parser.rl +68 -0
  115. data/lib/mail/parsers/phrase_lists_parser.rb +884 -0
  116. data/lib/mail/parsers/phrase_lists_parser.rl +90 -0
  117. data/lib/mail/parsers/received_parser.rb +8782 -0
  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/rfc5322_date_time.rl +37 -0
  128. data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
  129. data/lib/mail/parsers.rb +13 -0
  130. data/lib/mail/part.rb +11 -12
  131. data/lib/mail/parts_list.rb +90 -14
  132. data/lib/mail/smtp_envelope.rb +57 -0
  133. data/lib/mail/utilities.rb +415 -76
  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 +127 -79
  139. data/CHANGELOG.rdoc +0 -742
  140. data/CONTRIBUTING.md +0 -45
  141. data/Dependencies.txt +0 -3
  142. data/Gemfile +0 -32
  143. data/Rakefile +0 -21
  144. data/TODO.rdoc +0 -9
  145. data/lib/VERSION +0 -4
  146. data/lib/load_parsers.rb +0 -35
  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/smtp.rb +0 -24
  150. data/lib/mail/core_extensions/string/access.rb +0 -145
  151. data/lib/mail/core_extensions/string/multibyte.rb +0 -78
  152. data/lib/mail/core_extensions/string.rb +0 -33
  153. data/lib/mail/fields/common/address_container.rb +0 -16
  154. data/lib/mail/fields/common/common_address.rb +0 -140
  155. data/lib/mail/fields/common/common_date.rb +0 -42
  156. data/lib/mail/fields/common/common_field.rb +0 -57
  157. data/lib/mail/fields/common/common_message_id.rb +0 -48
  158. data/lib/mail/multibyte/exceptions.rb +0 -8
  159. data/lib/mail/parsers/address_lists.rb +0 -64
  160. data/lib/mail/parsers/address_lists.treetop +0 -19
  161. data/lib/mail/parsers/content_disposition.rb +0 -535
  162. data/lib/mail/parsers/content_disposition.treetop +0 -46
  163. data/lib/mail/parsers/content_location.rb +0 -139
  164. data/lib/mail/parsers/content_location.treetop +0 -20
  165. data/lib/mail/parsers/content_transfer_encoding.rb +0 -201
  166. data/lib/mail/parsers/content_transfer_encoding.treetop +0 -18
  167. data/lib/mail/parsers/content_type.rb +0 -971
  168. data/lib/mail/parsers/content_type.treetop +0 -68
  169. data/lib/mail/parsers/date_time.rb +0 -114
  170. data/lib/mail/parsers/date_time.treetop +0 -11
  171. data/lib/mail/parsers/envelope_from.rb +0 -194
  172. data/lib/mail/parsers/envelope_from.treetop +0 -32
  173. data/lib/mail/parsers/message_ids.rb +0 -45
  174. data/lib/mail/parsers/message_ids.treetop +0 -15
  175. data/lib/mail/parsers/mime_version.rb +0 -144
  176. data/lib/mail/parsers/mime_version.treetop +0 -19
  177. data/lib/mail/parsers/phrase_lists.rb +0 -45
  178. data/lib/mail/parsers/phrase_lists.treetop +0 -15
  179. data/lib/mail/parsers/received.rb +0 -71
  180. data/lib/mail/parsers/received.treetop +0 -11
  181. data/lib/mail/parsers/rfc2045.rb +0 -421
  182. data/lib/mail/parsers/rfc2045.treetop +0 -35
  183. data/lib/mail/parsers/rfc2822.rb +0 -5397
  184. data/lib/mail/parsers/rfc2822.treetop +0 -408
  185. data/lib/mail/parsers/rfc2822_obsolete.rb +0 -3768
  186. data/lib/mail/parsers/rfc2822_obsolete.treetop +0 -241
  187. data/lib/mail/patterns.rb +0 -35
  188. data/lib/mail/version_specific/ruby_1_8.rb +0 -119
  189. data/lib/mail/version_specific/ruby_1_9.rb +0 -147
  190. data/lib/tasks/corpus.rake +0 -125
  191. data/lib/tasks/treetop.rake +0 -10
@@ -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
@@ -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
@@ -6,8 +7,7 @@ module Mail
6
7
  end
7
8
 
8
9
  module Encodings
9
-
10
- include Mail::Patterns
10
+ include Mail::Constants
11
11
  extend Mail::Utilities
12
12
 
13
13
  @transfer_encodings = {}
@@ -18,7 +18,7 @@ module Mail
18
18
  #
19
19
  # Encodings.register "base64", Mail::Encodings::Base64
20
20
  def Encodings.register(name, cls)
21
- @transfer_encodings[get_name(name)] = cls
21
+ @transfer_encodings[get_name(name)] = cls
22
22
  end
23
23
 
24
24
  # Is the encoding we want defined?
@@ -26,8 +26,8 @@ module Mail
26
26
  # Example:
27
27
  #
28
28
  # Encodings.defined?(:base64) #=> true
29
- def Encodings.defined?( str )
30
- @transfer_encodings.include? get_name(str)
29
+ def Encodings.defined?(name)
30
+ @transfer_encodings.include? get_name(name)
31
31
  end
32
32
 
33
33
  # Gets a defined encoding type, QuotedPrintable or Base64 for now.
@@ -38,16 +38,24 @@ module Mail
38
38
  # Example:
39
39
  #
40
40
  # Encodings.get_encoding(:base64) #=> Mail::Encodings::Base64
41
- def Encodings.get_encoding( str )
42
- @transfer_encodings[get_name(str)]
41
+ def Encodings.get_encoding(name)
42
+ @transfer_encodings[get_name(name)]
43
43
  end
44
44
 
45
45
  def Encodings.get_all
46
46
  @transfer_encodings.values
47
47
  end
48
48
 
49
- def Encodings.get_name(enc)
50
- enc = enc.to_s.gsub("-", "_").downcase
49
+ def Encodings.get_name(name)
50
+ underscoreize(name).downcase
51
+ end
52
+
53
+ def Encodings.transcode_charset(str, from_charset, to_charset = 'UTF-8')
54
+ if from_charset
55
+ Utilities.transcode_charset str, from_charset, to_charset
56
+ else
57
+ str
58
+ end
51
59
  end
52
60
 
53
61
  # Encodes a parameter value using URI Escaping, note the language field 'en' can
@@ -57,8 +65,7 @@ module Mail
57
65
  # param_encode_language 'jp'
58
66
  # end
59
67
  #
60
- # The character set used for encoding will either be the value of $KCODE for
61
- # Ruby < 1.9 or the encoding on the string passed in.
68
+ # The character set used for encoding will be the encoding on the string passed in.
62
69
  #
63
70
  # Example:
64
71
  #
@@ -70,7 +77,7 @@ module Mail
70
77
  when str.ascii_only?
71
78
  str
72
79
  else
73
- RubyVer.param_encode(str)
80
+ Utilities.param_encode(str)
74
81
  end
75
82
  end
76
83
 
@@ -84,15 +91,15 @@ module Mail
84
91
  # str.encoding #=> 'ISO-8859-1' ## Only on Ruby 1.9
85
92
  # str #=> "This is fun"
86
93
  def Encodings.param_decode(str, encoding)
87
- RubyVer.param_decode(str, encoding)
94
+ Utilities.param_decode(str, encoding)
88
95
  end
89
96
 
90
97
  # Decodes or encodes a string as needed for either Base64 or QP encoding types in
91
98
  # the =?<encoding>?[QB]?<string>?=" format.
92
99
  #
93
100
  # The output type needs to be :decode to decode the input string or :encode to
94
- # encode the input string. The character set used for encoding will either be
95
- # the value of $KCODE for Ruby < 1.9 or the encoding on the string passed in.
101
+ # encode the input string. The character set used for encoding will be the
102
+ # encoding on the string passed in.
96
103
  #
97
104
  # On encoding, will only send out Base64 encoded strings.
98
105
  def Encodings.decode_encode(str, output_type)
@@ -103,7 +110,7 @@ module Mail
103
110
  if str.ascii_only?
104
111
  str
105
112
  else
106
- Encodings.b_value_encode(str, find_encoding(str))
113
+ Encodings.b_value_encode(str, str.encoding)
107
114
  end
108
115
  end
109
116
  end
@@ -114,34 +121,19 @@ module Mail
114
121
  # String has to be of the format =?<encoding>?[QB]?<string>?=
115
122
  def Encodings.value_decode(str)
116
123
  # Optimization: If there's no encoded-words in the string, just return it
117
- return str unless str =~ /\=\?[^?]+\?[QB]\?[^?]+?\?\=/xmi
124
+ return str unless str =~ ENCODED_VALUE
118
125
 
119
126
  lines = collapse_adjacent_encodings(str)
120
127
 
121
128
  # Split on white-space boundaries with capture, so we capture the white-space as well
122
- lines.map do |line|
123
- line.split(/([ \t])/).map do |text|
124
- if text.index('=?').nil?
125
- text
126
- else
127
- # Search for occurences of quoted strings or plain strings
128
- text.scan(/( # Group around entire regex to include it in matches
129
- \=\?[^?]+\?([QB])\?[^?]+?\?\= # Quoted String with subgroup for encoding method
130
- | # or
131
- .+?(?=\=\?|$) # Plain String
132
- )/xmi).map do |matches|
133
- string, method = *matches
134
- if method == 'b' || method == 'B'
135
- b_value_decode(string)
136
- elsif method == 'q' || method == 'Q'
137
- q_value_decode(string)
138
- else
139
- string
140
- end
141
- end
129
+ lines.each do |line|
130
+ line.gsub!(ENCODED_VALUE) do |string|
131
+ case $2
132
+ when *B_VALUES then b_value_decode(string)
133
+ when *Q_VALUES then q_value_decode(string)
142
134
  end
143
135
  end
144
- end.flatten.join("")
136
+ end.join("")
145
137
  end
146
138
 
147
139
  # Takes an encoded string of the format =?<encoding>?[QB]?<string>?=
@@ -152,13 +144,8 @@ module Mail
152
144
  output
153
145
  elsif to_encoding
154
146
  begin
155
- if RUBY_VERSION >= '1.9'
156
- output.encode(to_encoding)
157
- else
158
- require 'iconv'
159
- Iconv.iconv(to_encoding, 'UTF-8', output).first
160
- end
161
- rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
147
+ output.encode(to_encoding)
148
+ rescue Errno::EINVAL
162
149
  # the 'from' parameter specifies a charset other than what the text
163
150
  # actually is...not much we can do in this case but just return the
164
151
  # unconverted text.
@@ -174,21 +161,21 @@ module Mail
174
161
 
175
162
  def Encodings.address_encode(address, charset = 'utf-8')
176
163
  if address.is_a?(Array)
177
- # loop back through for each element
178
164
  address.compact.map { |a| Encodings.address_encode(a, charset) }.join(", ")
179
- else
180
- # find any word boundary that is not ascii and encode it
181
- encode_non_usascii(address, charset) if address
165
+ elsif address
166
+ encode_non_usascii(address, charset)
182
167
  end
183
168
  end
184
169
 
185
170
  def Encodings.encode_non_usascii(address, charset)
186
171
  return address if address.ascii_only? or charset.nil?
187
- us_ascii = %Q{\x00-\x7f}
188
- # Encode any non usascii strings embedded inside of quotes
189
- address = address.gsub(/(".*?[^#{us_ascii}].*?")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
172
+
173
+ # Encode all strings embedded inside of quotes
174
+ address = address.gsub(/("[^"]*[^\/]")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
175
+
190
176
  # Then loop through all remaining items and encode as needed
191
177
  tokens = address.split(/\s/)
178
+
192
179
  map_with_index(tokens) do |word, i|
193
180
  if word.ascii_only?
194
181
  word
@@ -209,12 +196,15 @@ module Mail
209
196
  #
210
197
  # Encodings.b_value_encode('This is あ string', 'UTF-8')
211
198
  # #=> "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?="
212
- def Encodings.b_value_encode(encoded_str, encoding = nil)
213
- return encoded_str if encoded_str.to_s.ascii_only?
214
- string, encoding = RubyVer.b_value_encode(encoded_str, encoding)
215
- map_lines(string) do |str|
216
- "=?#{encoding}?B?#{str.chomp}?="
217
- end.join(" ")
199
+ def Encodings.b_value_encode(string, encoding = nil)
200
+ if string.to_s.ascii_only?
201
+ string
202
+ else
203
+ Encodings.each_base64_chunk_byterange(string, 60).map do |chunk|
204
+ str, encoding = Utilities.b_value_encode(chunk, encoding)
205
+ "=?#{encoding}?B?#{str.chomp}?="
206
+ end.join(" ")
207
+ end
218
208
  end
219
209
 
220
210
  # Encode a string with Quoted-Printable Encoding and returns it ready to be inserted
@@ -226,7 +216,7 @@ module Mail
226
216
  # #=> "=?UTF-8?Q?This_is_=E3=81=82_string?="
227
217
  def Encodings.q_value_encode(encoded_str, encoding = nil)
228
218
  return encoded_str if encoded_str.to_s.ascii_only?
229
- string, encoding = RubyVer.q_value_encode(encoded_str, encoding)
219
+ string, encoding = Utilities.q_value_encode(encoded_str, encoding)
230
220
  string.gsub!("=\r\n", '') # We already have limited the string to the length we want
231
221
  map_lines(string) do |str|
232
222
  "=?#{encoding}?Q?#{str.chomp.gsub(/ /, '_')}?="
@@ -242,7 +232,7 @@ module Mail
242
232
  # Encodings.b_value_decode("=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=")
243
233
  # #=> 'This is あ string'
244
234
  def Encodings.b_value_decode(str)
245
- RubyVer.b_value_decode(str)
235
+ Utilities.b_value_decode(str)
246
236
  end
247
237
 
248
238
  # Decodes a Quoted-Printable string from the "=?UTF-8?Q?This_is_=E3=81=82_string?=" format
@@ -252,54 +242,73 @@ module Mail
252
242
  # Encodings.q_value_decode("=?UTF-8?Q?This_is_=E3=81=82_string?=")
253
243
  # #=> 'This is あ string'
254
244
  def Encodings.q_value_decode(str)
255
- RubyVer.q_value_decode(str)
256
- end
257
-
258
- def Encodings.split_encoding_from_string( str )
259
- match = str.match(/\=\?([^?]+)?\?[QB]\?(.+)?\?\=/mi)
260
- if match
261
- match[1]
262
- else
263
- nil
264
- end
265
- end
266
-
267
- def Encodings.find_encoding(str)
268
- RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
245
+ Utilities.q_value_decode(str)
269
246
  end
270
247
 
271
248
  # Gets the encoding type (Q or B) from the string.
272
- def Encodings.split_value_encoding_from_string(str)
273
- match = str.match(/\=\?[^?]+?\?([QB])\?(.+)?\?\=/mi)
274
- if match
275
- match[1]
276
- else
277
- nil
278
- end
249
+ def Encodings.value_encoding_from_string(str)
250
+ str[ENCODED_VALUE, 1]
279
251
  end
280
252
 
281
- # When the encoded string consists of multiple lines, lines with the same
282
- # encoding (Q or B) can be joined together.
253
+ # Split header line into proper encoded and unencoded parts.
283
254
  #
284
255
  # String has to be of the format =?<encoding>?[QB]?<string>?=
256
+ #
257
+ # Omit unencoded space after an encoded-word.
285
258
  def Encodings.collapse_adjacent_encodings(str)
286
- lines = str.split(/(\?=)\s*(=\?)/).each_slice(2).map(&:join)
287
259
  results = []
288
- previous_encoding = nil
260
+ last_encoded = nil # Track whether to preserve or drop whitespace
289
261
 
290
- lines.each do |line|
291
- encoding = split_value_encoding_from_string(line)
262
+ lines = str.split(FULL_ENCODED_VALUE)
263
+ lines.each_slice(2) do |unencoded, encoded|
264
+ if last_encoded = encoded
265
+ if !Utilities.blank?(unencoded) || (!last_encoded && unencoded != EMPTY)
266
+ results << unencoded
267
+ end
292
268
 
293
- if encoding == previous_encoding
294
- line = results.pop + line
295
- line.gsub!(/\?\=\=\?.+?\?[QqBb]\?/m, '')
269
+ results << encoded
270
+ else
271
+ results << unencoded
296
272
  end
297
-
298
- previous_encoding = encoding
299
- results << line
300
273
  end
301
274
 
302
275
  results
303
276
  end
277
+
278
+ # Partition the string into bounded-size chunks without splitting
279
+ # multibyte characters.
280
+ def Encodings.each_base64_chunk_byterange(str, max_bytesize_per_base64_chunk, &block)
281
+ raise "size per chunk must be multiple of 4" if (max_bytesize_per_base64_chunk % 4).nonzero?
282
+
283
+ if block_given?
284
+ max_bytesize = ((3 * max_bytesize_per_base64_chunk) / 4.0).floor
285
+ each_chunk_byterange(str, max_bytesize, &block)
286
+ else
287
+ enum_for :each_base64_chunk_byterange, str, max_bytesize_per_base64_chunk
288
+ end
289
+ end
290
+
291
+ # Partition the string into bounded-size chunks without splitting
292
+ # multibyte characters.
293
+ def Encodings.each_chunk_byterange(str, max_bytesize_per_chunk)
294
+ return enum_for(:each_chunk_byterange, str, max_bytesize_per_chunk) unless block_given?
295
+
296
+ offset = 0
297
+ chunksize = 0
298
+
299
+ str.each_char do |chr|
300
+ charsize = chr.bytesize
301
+
302
+ if chunksize + charsize > max_bytesize_per_chunk
303
+ yield Utilities.string_byteslice(str, offset, chunksize)
304
+ offset += chunksize
305
+ chunksize = charsize
306
+ else
307
+ chunksize += charsize
308
+ end
309
+ end
310
+
311
+ yield Utilities.string_byteslice(str, offset, chunksize)
312
+ end
304
313
  end
305
314
  end
data/lib/mail/envelope.rb CHANGED
@@ -1,35 +1,28 @@
1
1
  # encoding: utf-8
2
- #
2
+ # frozen_string_literal: true
3
+ #
3
4
  # = Mail Envelope
4
- #
5
+ #
5
6
  # The Envelope class provides a field for the first line in an
6
7
  # mbox file, that looks like "From mikel@test.lindsaar.net DATETIME"
7
- #
8
+ #
8
9
  # This envelope class reads that line, and turns it into an
9
10
  # Envelope.from and Envelope.date for your use.
11
+
10
12
  module Mail
11
- class Envelope < StructuredField
12
-
13
- def initialize(*args)
14
- super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
15
- end
16
-
17
- def tree
18
- @element ||= Mail::EnvelopeFromElement.new(value)
19
- @tree ||= @element.tree
20
- end
21
-
13
+ class Envelope < NamedStructuredField
14
+ NAME = 'Envelope-From'
15
+
22
16
  def element
23
17
  @element ||= Mail::EnvelopeFromElement.new(value)
24
18
  end
25
-
26
- def date
27
- ::DateTime.parse("#{element.date_time}")
28
- end
29
19
 
30
20
  def from
31
21
  element.address
32
22
  end
33
-
23
+
24
+ def date
25
+ element.date_time
26
+ end
34
27
  end
35
28
  end