mail 2.6.6 → 2.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/MIT-LICENSE +1 -1
- data/README.md +74 -90
- data/lib/mail/attachments_list.rb +8 -4
- data/lib/mail/body.rb +50 -38
- data/lib/mail/check_delivery_params.rb +8 -6
- data/lib/mail/configuration.rb +2 -0
- data/lib/mail/constants.rb +1 -1
- data/lib/mail/core_extensions/smtp.rb +19 -16
- data/lib/mail/core_extensions/string.rb +0 -4
- data/lib/mail/elements/address.rb +28 -22
- data/lib/mail/elements/address_list.rb +10 -18
- data/lib/mail/elements/content_disposition_element.rb +8 -15
- data/lib/mail/elements/content_location_element.rb +5 -10
- data/lib/mail/elements/content_transfer_encoding_element.rb +5 -10
- data/lib/mail/elements/content_type_element.rb +8 -19
- data/lib/mail/elements/date_time_element.rb +6 -14
- data/lib/mail/elements/envelope_from_element.rb +14 -21
- data/lib/mail/elements/message_ids_element.rb +8 -12
- data/lib/mail/elements/mime_version_element.rb +6 -14
- data/lib/mail/elements/phrase_list.rb +6 -9
- data/lib/mail/elements/received_element.rb +9 -15
- data/lib/mail/encodings/7bit.rb +5 -15
- data/lib/mail/encodings/8bit.rb +2 -21
- data/lib/mail/encodings/base64.rb +11 -12
- data/lib/mail/encodings/binary.rb +3 -22
- data/lib/mail/encodings/identity.rb +24 -0
- data/lib/mail/encodings/quoted_printable.rb +6 -6
- data/lib/mail/encodings/transfer_encoding.rb +38 -29
- data/lib/mail/encodings/unix_to_unix.rb +3 -1
- data/lib/mail/encodings.rb +99 -43
- data/lib/mail/envelope.rb +1 -1
- data/lib/mail/field.rb +96 -59
- data/lib/mail/fields/bcc_field.rb +2 -2
- data/lib/mail/fields/cc_field.rb +1 -1
- data/lib/mail/fields/comments_field.rb +1 -1
- data/lib/mail/fields/common/common_address.rb +32 -7
- data/lib/mail/fields/common/common_field.rb +1 -10
- data/lib/mail/fields/common/parameter_hash.rb +1 -1
- data/lib/mail/fields/content_description_field.rb +1 -1
- data/lib/mail/fields/content_disposition_field.rb +3 -3
- data/lib/mail/fields/content_id_field.rb +2 -2
- data/lib/mail/fields/content_location_field.rb +1 -1
- data/lib/mail/fields/content_transfer_encoding_field.rb +1 -1
- data/lib/mail/fields/content_type_field.rb +4 -9
- data/lib/mail/fields/date_field.rb +2 -3
- data/lib/mail/fields/from_field.rb +1 -1
- data/lib/mail/fields/in_reply_to_field.rb +1 -1
- data/lib/mail/fields/keywords_field.rb +1 -1
- data/lib/mail/fields/message_id_field.rb +1 -1
- data/lib/mail/fields/mime_version_field.rb +1 -1
- data/lib/mail/fields/optional_field.rb +4 -1
- data/lib/mail/fields/received_field.rb +1 -1
- data/lib/mail/fields/references_field.rb +1 -1
- data/lib/mail/fields/reply_to_field.rb +1 -1
- data/lib/mail/fields/resent_bcc_field.rb +1 -1
- data/lib/mail/fields/resent_cc_field.rb +1 -1
- data/lib/mail/fields/resent_date_field.rb +0 -1
- data/lib/mail/fields/resent_from_field.rb +1 -1
- data/lib/mail/fields/resent_message_id_field.rb +1 -1
- data/lib/mail/fields/resent_sender_field.rb +1 -1
- data/lib/mail/fields/resent_to_field.rb +1 -1
- data/lib/mail/fields/return_path_field.rb +1 -1
- data/lib/mail/fields/sender_field.rb +1 -1
- data/lib/mail/fields/subject_field.rb +1 -1
- data/lib/mail/fields/to_field.rb +1 -1
- data/lib/mail/fields/unstructured_field.rb +21 -4
- data/lib/mail/header.rb +10 -8
- data/lib/mail/mail.rb +2 -10
- data/lib/mail/matchers/has_sent_mail.rb +21 -1
- data/lib/mail/message.rb +78 -68
- data/lib/mail/multibyte/chars.rb +29 -28
- data/lib/mail/multibyte/unicode.rb +10 -10
- data/lib/mail/multibyte.rb +64 -15
- data/lib/mail/network/delivery_methods/logger_delivery.rb +37 -0
- data/lib/mail/network/delivery_methods/sendmail.rb +8 -5
- data/lib/mail/network/delivery_methods/smtp.rb +58 -49
- data/lib/mail/network/delivery_methods/smtp_connection.rb +9 -1
- data/lib/mail/network/retriever_methods/imap.rb +18 -5
- data/lib/mail/network/retriever_methods/pop3.rb +3 -1
- data/lib/mail/network.rb +1 -0
- data/lib/mail/parser_tools.rb +15 -0
- data/lib/mail/parsers/address_lists_parser.rb +33207 -104
- data/lib/mail/parsers/address_lists_parser.rl +172 -0
- data/lib/mail/parsers/content_disposition_parser.rb +876 -49
- data/lib/mail/parsers/content_disposition_parser.rl +82 -0
- data/lib/mail/parsers/content_location_parser.rb +803 -23
- data/lib/mail/parsers/content_location_parser.rl +71 -0
- data/lib/mail/parsers/content_transfer_encoding_parser.rb +501 -19
- data/lib/mail/parsers/content_transfer_encoding_parser.rl +64 -0
- data/lib/mail/parsers/content_type_parser.rb +1023 -48
- data/lib/mail/parsers/content_type_parser.rl +83 -0
- data/lib/mail/parsers/date_time_parser.rb +870 -24
- data/lib/mail/parsers/date_time_parser.rl +62 -0
- data/lib/mail/parsers/envelope_from_parser.rb +3569 -34
- data/lib/mail/parsers/envelope_from_parser.rl +82 -0
- data/lib/mail/parsers/message_ids_parser.rb +2839 -25
- data/lib/mail/parsers/message_ids_parser.rl +82 -0
- data/lib/mail/parsers/mime_version_parser.rb +491 -26
- data/lib/mail/parsers/mime_version_parser.rl +61 -0
- data/lib/mail/parsers/phrase_lists_parser.rb +860 -18
- data/lib/mail/parsers/phrase_lists_parser.rl +83 -0
- data/lib/mail/parsers/received_parser.rb +8764 -37
- data/lib/mail/parsers/received_parser.rl +84 -0
- data/lib/mail/parsers/rfc2045_content_transfer_encoding.rl +13 -0
- data/lib/mail/parsers/rfc2045_content_type.rl +25 -0
- data/lib/mail/parsers/rfc2045_mime.rl +16 -0
- data/lib/mail/parsers/rfc2183_content_disposition.rl +15 -0
- data/lib/mail/parsers/rfc3629_utf8.rl +19 -0
- data/lib/mail/parsers/rfc5234_abnf_core_rules.rl +22 -0
- data/lib/mail/parsers/rfc5322.rl +59 -0
- data/lib/mail/parsers/rfc5322_address.rl +72 -0
- data/lib/mail/parsers/{ragel/date_time.rl → rfc5322_date_time.rl} +8 -1
- data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
- data/lib/mail/parsers.rb +16 -24
- data/lib/mail/part.rb +3 -3
- data/lib/mail/parts_list.rb +5 -6
- data/lib/mail/utilities.rb +59 -28
- data/lib/mail/version.rb +2 -2
- data/lib/mail/version_specific/ruby_1_8.rb +40 -3
- data/lib/mail/version_specific/ruby_1_9.rb +61 -9
- data/lib/mail.rb +3 -16
- metadata +44 -53
- data/CHANGELOG.rdoc +0 -803
- data/CONTRIBUTING.md +0 -60
- data/Dependencies.txt +0 -2
- data/Gemfile +0 -14
- data/Rakefile +0 -29
- data/TODO.rdoc +0 -9
- data/lib/mail/core_extensions/string/access.rb +0 -146
- data/lib/mail/core_extensions/string/multibyte.rb +0 -79
- data/lib/mail/multibyte/exceptions.rb +0 -9
- data/lib/mail/parsers/ragel/common.rl +0 -185
- data/lib/mail/parsers/ragel/parser_info.rb +0 -61
- data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +0 -14864
- data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +0 -37
- data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +0 -751
- data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +0 -37
- data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +0 -614
- data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +0 -37
- data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +0 -447
- data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +0 -37
- data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +0 -825
- data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +0 -37
- data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +0 -817
- data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +0 -37
- data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +0 -2149
- data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +0 -37
- data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +0 -1570
- data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +0 -37
- data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +0 -440
- data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +0 -37
- data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +0 -564
- data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +0 -37
- data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +0 -51
- data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +0 -5144
- data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +0 -37
- data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +0 -37
- data/lib/mail/parsers/ragel/ruby.rb +0 -40
- data/lib/mail/parsers/ragel.rb +0 -18
@@ -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
|
@@ -6,27 +6,27 @@ module Mail
|
|
6
6
|
module Encodings
|
7
7
|
class QuotedPrintable < SevenBit
|
8
8
|
NAME='quoted-printable'
|
9
|
-
|
9
|
+
|
10
10
|
PRIORITY = 2
|
11
11
|
|
12
|
-
def self.can_encode?(
|
13
|
-
EightBit.can_encode?
|
12
|
+
def self.can_encode?(enc)
|
13
|
+
EightBit.can_encode? enc
|
14
14
|
end
|
15
15
|
|
16
16
|
# Decode the string from Quoted-Printable. Cope with hard line breaks
|
17
17
|
# that were incorrectly encoded as hex instead of literal CRLF.
|
18
18
|
def self.decode(str)
|
19
|
-
|
19
|
+
str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.encode(str)
|
23
|
-
|
23
|
+
[str].pack("M")
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.cost(str)
|
27
27
|
# These bytes probably do not need encoding
|
28
28
|
c = str.count("\x9\xA\xD\x20-\x3C\x3E-\x7E")
|
29
|
-
# Everything else turns into =XX where XX is a
|
29
|
+
# Everything else turns into =XX where XX is a
|
30
30
|
# two digit hex number (taking 3 bytes)
|
31
31
|
total = (str.bytesize - c)*3 + c
|
32
32
|
total.to_f/str.bytesize
|
@@ -7,17 +7,17 @@ module Mail
|
|
7
7
|
|
8
8
|
PRIORITY = -1
|
9
9
|
|
10
|
+
# And encoding's superclass can always transport it since the
|
11
|
+
# class hierarchy is arranged e.g. Base64 < 7bit < 8bit < Binary.
|
10
12
|
def self.can_transport?(enc)
|
11
|
-
enc
|
12
|
-
if Encodings.defined? enc
|
13
|
-
Encodings.get_encoding(enc).new.is_a? self
|
14
|
-
else
|
15
|
-
false
|
16
|
-
end
|
13
|
+
enc && enc <= self
|
17
14
|
end
|
18
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.
|
19
19
|
def self.can_encode?(enc)
|
20
|
-
can_transport? enc
|
20
|
+
can_transport? enc
|
21
21
|
end
|
22
22
|
|
23
23
|
def self.cost(str)
|
@@ -32,36 +32,45 @@ module Mail
|
|
32
32
|
self::NAME
|
33
33
|
end
|
34
34
|
|
35
|
-
def self.
|
36
|
-
|
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)
|
37
40
|
source_encoding
|
38
41
|
else
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
+
renegotiate(message_encoding, source_encoding, str, allowed_encodings)
|
43
|
+
end
|
44
|
+
end
|
42
45
|
|
43
|
-
|
44
|
-
|
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
|
45
52
|
|
46
|
-
|
47
|
-
|
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)
|
53
|
+
lowest_cost(str, encodings)
|
54
|
+
end
|
50
55
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
elsif this_cost == best_cost
|
55
|
-
best = enc if enc::PRIORITY < best::PRIORITY
|
56
|
-
end
|
57
|
-
end
|
56
|
+
def self.lowest_cost(str, encodings)
|
57
|
+
best = nil
|
58
|
+
best_cost = nil
|
58
59
|
|
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
|
60
71
|
end
|
61
|
-
end
|
62
72
|
|
63
|
-
|
64
|
-
self.class.to_s
|
73
|
+
best
|
65
74
|
end
|
66
75
|
end
|
67
76
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module Mail
|
3
3
|
module Encodings
|
4
|
-
|
4
|
+
class UnixToUnix < TransferEncoding
|
5
5
|
NAME = "x-uuencode"
|
6
6
|
|
7
7
|
def self.decode(str)
|
@@ -13,6 +13,8 @@ module Mail
|
|
13
13
|
end
|
14
14
|
|
15
15
|
Encodings.register(NAME, self)
|
16
|
+
Encodings.register("uuencode", self)
|
17
|
+
Encodings.register("x-uue", self)
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
data/lib/mail/encodings.rb
CHANGED
@@ -7,7 +7,6 @@ module Mail
|
|
7
7
|
end
|
8
8
|
|
9
9
|
module Encodings
|
10
|
-
|
11
10
|
include Mail::Constants
|
12
11
|
extend Mail::Utilities
|
13
12
|
|
@@ -19,7 +18,7 @@ module Mail
|
|
19
18
|
#
|
20
19
|
# Encodings.register "base64", Mail::Encodings::Base64
|
21
20
|
def Encodings.register(name, cls)
|
22
|
-
|
21
|
+
@transfer_encodings[get_name(name)] = cls
|
23
22
|
end
|
24
23
|
|
25
24
|
# Is the encoding we want defined?
|
@@ -27,8 +26,8 @@ module Mail
|
|
27
26
|
# Example:
|
28
27
|
#
|
29
28
|
# Encodings.defined?(:base64) #=> true
|
30
|
-
def Encodings.defined?(
|
31
|
-
@transfer_encodings.include? get_name(
|
29
|
+
def Encodings.defined?(name)
|
30
|
+
@transfer_encodings.include? get_name(name)
|
32
31
|
end
|
33
32
|
|
34
33
|
# Gets a defined encoding type, QuotedPrintable or Base64 for now.
|
@@ -39,16 +38,16 @@ module Mail
|
|
39
38
|
# Example:
|
40
39
|
#
|
41
40
|
# Encodings.get_encoding(:base64) #=> Mail::Encodings::Base64
|
42
|
-
def Encodings.get_encoding(
|
43
|
-
@transfer_encodings[get_name(
|
41
|
+
def Encodings.get_encoding(name)
|
42
|
+
@transfer_encodings[get_name(name)]
|
44
43
|
end
|
45
44
|
|
46
45
|
def Encodings.get_all
|
47
46
|
@transfer_encodings.values
|
48
47
|
end
|
49
48
|
|
50
|
-
def Encodings.get_name(
|
51
|
-
underscoreize(
|
49
|
+
def Encodings.get_name(name)
|
50
|
+
underscoreize(name).downcase
|
52
51
|
end
|
53
52
|
|
54
53
|
def Encodings.transcode_charset(str, from_charset, to_charset = 'UTF-8')
|
@@ -168,32 +167,51 @@ module Mail
|
|
168
167
|
|
169
168
|
def Encodings.address_encode(address, charset = 'utf-8')
|
170
169
|
if address.is_a?(Array)
|
171
|
-
# loop back through for each element
|
172
170
|
address.compact.map { |a| Encodings.address_encode(a, charset) }.join(", ")
|
173
|
-
|
174
|
-
|
175
|
-
encode_non_usascii(address, charset) if address
|
171
|
+
elsif address
|
172
|
+
encode_non_usascii(address, charset)
|
176
173
|
end
|
177
174
|
end
|
178
175
|
|
179
176
|
def Encodings.encode_non_usascii(address, charset)
|
180
177
|
return address if address.ascii_only? or charset.nil?
|
181
|
-
|
182
|
-
#
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
if
|
192
|
-
word
|
178
|
+
|
179
|
+
# With KCODE=u we can't use regexps on other encodings. Go ASCII.
|
180
|
+
with_ascii_kcode do
|
181
|
+
# Encode all strings embedded inside of quotes
|
182
|
+
address = address.gsub(/("[^"]*[^\/]")/) { |s| Encodings.b_value_encode(unquote(s), charset) }
|
183
|
+
|
184
|
+
# Then loop through all remaining items and encode as needed
|
185
|
+
tokens = address.split(/\s/)
|
186
|
+
|
187
|
+
map_with_index(tokens) do |word, i|
|
188
|
+
if word.ascii_only?
|
189
|
+
word
|
190
|
+
else
|
191
|
+
previous_non_ascii = i>0 && tokens[i-1] && !tokens[i-1].ascii_only?
|
192
|
+
if previous_non_ascii #why are we adding an extra space here?
|
193
|
+
word = " #{word}"
|
194
|
+
end
|
195
|
+
Encodings.b_value_encode(word, charset)
|
193
196
|
end
|
194
|
-
|
197
|
+
end.join(' ')
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
if RUBY_VERSION < '1.9'
|
202
|
+
# With KCODE=u we can't use regexps on other encodings. Go ASCII.
|
203
|
+
def Encodings.with_ascii_kcode #:nodoc:
|
204
|
+
if $KCODE
|
205
|
+
$KCODE, original_kcode = '', $KCODE
|
195
206
|
end
|
196
|
-
|
207
|
+
yield
|
208
|
+
ensure
|
209
|
+
$KCODE = original_kcode if original_kcode
|
210
|
+
end
|
211
|
+
else
|
212
|
+
def Encodings.with_ascii_kcode #:nodoc:
|
213
|
+
yield
|
214
|
+
end
|
197
215
|
end
|
198
216
|
|
199
217
|
# Encode a string with Base64 Encoding and returns it ready to be inserted
|
@@ -203,12 +221,15 @@ module Mail
|
|
203
221
|
#
|
204
222
|
# Encodings.b_value_encode('This is あ string', 'UTF-8')
|
205
223
|
# #=> "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?="
|
206
|
-
def Encodings.b_value_encode(
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
224
|
+
def Encodings.b_value_encode(string, encoding = nil)
|
225
|
+
if string.to_s.ascii_only?
|
226
|
+
string
|
227
|
+
else
|
228
|
+
Encodings.each_base64_chunk_byterange(string, 60).map do |chunk|
|
229
|
+
str, encoding = RubyVer.b_value_encode(chunk, encoding)
|
230
|
+
"=?#{encoding}?B?#{str.chomp}?="
|
231
|
+
end.join(" ")
|
232
|
+
end
|
212
233
|
end
|
213
234
|
|
214
235
|
# Encode a string with Quoted-Printable Encoding and returns it ready to be inserted
|
@@ -258,24 +279,23 @@ module Mail
|
|
258
279
|
str[ENCODED_VALUE, 1]
|
259
280
|
end
|
260
281
|
|
261
|
-
#
|
262
|
-
# encoding (Q or B) can be joined together.
|
282
|
+
# Split header line into proper encoded and unencoded parts.
|
263
283
|
#
|
264
284
|
# String has to be of the format =?<encoding>?[QB]?<string>?=
|
285
|
+
#
|
286
|
+
# Omit unencoded space after an encoded-word.
|
265
287
|
def Encodings.collapse_adjacent_encodings(str)
|
266
288
|
results = []
|
267
|
-
|
289
|
+
last_encoded = nil # Track whether to preserve or drop whitespace
|
290
|
+
|
268
291
|
lines = str.split(FULL_ENCODED_VALUE)
|
269
292
|
lines.each_slice(2) do |unencoded, encoded|
|
270
|
-
if encoded
|
271
|
-
|
272
|
-
|
273
|
-
results.last << encoded
|
274
|
-
else
|
275
|
-
results << unencoded unless unencoded == EMPTY
|
276
|
-
results << encoded
|
293
|
+
if last_encoded = encoded
|
294
|
+
if !Utilities.blank?(unencoded) || (!last_encoded && unencoded != EMPTY)
|
295
|
+
results << unencoded
|
277
296
|
end
|
278
|
-
|
297
|
+
|
298
|
+
results << encoded
|
279
299
|
else
|
280
300
|
results << unencoded
|
281
301
|
end
|
@@ -283,5 +303,41 @@ module Mail
|
|
283
303
|
|
284
304
|
results
|
285
305
|
end
|
306
|
+
|
307
|
+
# Partition the string into bounded-size chunks without splitting
|
308
|
+
# multibyte characters.
|
309
|
+
def Encodings.each_base64_chunk_byterange(str, max_bytesize_per_base64_chunk, &block)
|
310
|
+
raise "size per chunk must be multiple of 4" if (max_bytesize_per_base64_chunk % 4).nonzero?
|
311
|
+
|
312
|
+
if block_given?
|
313
|
+
max_bytesize = ((3 * max_bytesize_per_base64_chunk) / 4.0).floor
|
314
|
+
each_chunk_byterange(str, max_bytesize, &block)
|
315
|
+
else
|
316
|
+
enum_for :each_base64_chunk_byterange, str, max_bytesize_per_base64_chunk
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Partition the string into bounded-size chunks without splitting
|
321
|
+
# multibyte characters.
|
322
|
+
def Encodings.each_chunk_byterange(str, max_bytesize_per_chunk)
|
323
|
+
return enum_for(:each_chunk_byterange, str, max_bytesize_per_chunk) unless block_given?
|
324
|
+
|
325
|
+
offset = 0
|
326
|
+
chunksize = 0
|
327
|
+
|
328
|
+
str.each_char do |chr|
|
329
|
+
charsize = chr.bytesize
|
330
|
+
|
331
|
+
if chunksize + charsize > max_bytesize_per_chunk
|
332
|
+
yield RubyVer.string_byteslice(str, offset, chunksize)
|
333
|
+
offset += chunksize
|
334
|
+
chunksize = charsize
|
335
|
+
else
|
336
|
+
chunksize += charsize
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
yield RubyVer.string_byteslice(str, offset, chunksize)
|
341
|
+
end
|
286
342
|
end
|
287
343
|
end
|
data/lib/mail/envelope.rb
CHANGED
data/lib/mail/field.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'mail/fields'
|
3
|
+
require 'mail/constants'
|
3
4
|
|
4
5
|
# encoding: utf-8
|
5
6
|
module Mail
|
@@ -83,9 +84,31 @@ module Mail
|
|
83
84
|
|
84
85
|
def initialize(element, value, reason)
|
85
86
|
@element = element
|
86
|
-
@value = value
|
87
|
-
@reason = reason
|
88
|
-
super("#{element} can not parse |#{value}
|
87
|
+
@value = to_utf8(value)
|
88
|
+
@reason = to_utf8(reason)
|
89
|
+
super("#{@element} can not parse |#{@value}|: #{@reason}")
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
def to_utf8(text)
|
94
|
+
if text.respond_to?(:force_encoding)
|
95
|
+
text.dup.force_encoding(Encoding::UTF_8)
|
96
|
+
else
|
97
|
+
text
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class NilParseError < ParseError #:nodoc:
|
103
|
+
def initialize(element)
|
104
|
+
super element, nil, 'nil is invalid'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class IncompleteParseError < ParseError #:nodoc:
|
109
|
+
def initialize(element, original_text, unparsed_index)
|
110
|
+
parsed_text = to_utf8(original_text[0...unparsed_index])
|
111
|
+
super element, original_text, "Only able to parse up to #{parsed_text.inspect}"
|
89
112
|
end
|
90
113
|
end
|
91
114
|
|
@@ -93,41 +116,64 @@ module Mail
|
|
93
116
|
class SyntaxError < FieldError #:nodoc:
|
94
117
|
end
|
95
118
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
119
|
+
class << self
|
120
|
+
# Parse a field from a raw header line:
|
121
|
+
#
|
122
|
+
# Mail::Field.parse("field-name: field data")
|
123
|
+
# # => #<Mail::Field …>
|
124
|
+
def parse(field, charset = nil)
|
125
|
+
name, value = split(field)
|
126
|
+
if name && value
|
127
|
+
new name, value, charset
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def split(raw_field) #:nodoc:
|
132
|
+
if raw_field.index(Constants::COLON)
|
133
|
+
name, value = raw_field.split(Constants::COLON, 2)
|
134
|
+
name.rstrip!
|
135
|
+
if name =~ /\A#{Constants::FIELD_NAME}\z/
|
136
|
+
[ name.rstrip, value.strip ]
|
137
|
+
else
|
138
|
+
Kernel.warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: invalid header name syntax: #{name.inspect}"
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
else
|
142
|
+
raw_field.strip
|
143
|
+
end
|
144
|
+
rescue => error
|
145
|
+
warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: #{error.class}: #{error.message}"
|
146
|
+
nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
attr_reader :unparsed_value
|
151
|
+
|
152
|
+
# Create a field by name and optional value:
|
103
153
|
#
|
104
|
-
#
|
154
|
+
# Mail::Field.new("field-name", "value")
|
155
|
+
# # => #<Mail::Field …>
|
105
156
|
#
|
106
|
-
#
|
157
|
+
# Values that aren't strings or arrays are coerced to Strings with `#to_s`.
|
107
158
|
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
# it will be passed through as is, for example, content-type
|
111
|
-
# field can accept an array with the type and a hash of
|
112
|
-
# parameters:
|
159
|
+
# Mail::Field.new("field-name", 1234)
|
160
|
+
# # => #<Mail::Field …>
|
113
161
|
#
|
114
|
-
# Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
|
162
|
+
# Mail::Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
|
163
|
+
# # => #<Mail::Field …>
|
115
164
|
def initialize(name, value = nil, charset = 'utf-8')
|
116
165
|
case
|
117
|
-
when name.index(COLON)
|
166
|
+
when name.index(COLON)
|
167
|
+
Kernel.warn 'Passing an unparsed header field to Mail::Field.new is deprecated and will be removed in Mail 2.8.0. Use Mail::Field.parse instead.'
|
168
|
+
@name, @unparsed_value = self.class.split(name)
|
118
169
|
@charset = Utilities.blank?(value) ? charset : value
|
119
|
-
|
120
|
-
@raw_value = name
|
121
|
-
@value = nil
|
122
|
-
when Utilities.blank?(value) # Field.new("field-name")
|
170
|
+
when Utilities.blank?(value)
|
123
171
|
@name = name
|
124
|
-
@
|
125
|
-
@raw_value = nil
|
172
|
+
@unparsed_value = nil
|
126
173
|
@charset = charset
|
127
|
-
else
|
174
|
+
else
|
128
175
|
@name = name
|
129
|
-
@
|
130
|
-
@raw_value = nil
|
176
|
+
@unparsed_value = value
|
131
177
|
@charset = charset
|
132
178
|
end
|
133
179
|
@name = FIELD_NAME_MAP[@name.to_s.downcase] || @name
|
@@ -138,8 +184,7 @@ module Mail
|
|
138
184
|
end
|
139
185
|
|
140
186
|
def field
|
141
|
-
|
142
|
-
@field ||= create_field(@name, @value, @charset)
|
187
|
+
@field ||= create_field(@name, @unparsed_value, @charset)
|
143
188
|
end
|
144
189
|
|
145
190
|
def name
|
@@ -217,11 +262,26 @@ module Mail
|
|
217
262
|
|
218
263
|
private
|
219
264
|
|
220
|
-
def
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
265
|
+
def create_field(name, value, charset)
|
266
|
+
new_field(name, value, charset)
|
267
|
+
rescue Mail::Field::ParseError => e
|
268
|
+
field = Mail::UnstructuredField.new(name, value)
|
269
|
+
field.errors << [name, value, e]
|
270
|
+
field
|
271
|
+
end
|
272
|
+
|
273
|
+
def new_field(name, value, charset)
|
274
|
+
value = unfold(value) if value.is_a?(String)
|
275
|
+
|
276
|
+
if klass = field_class_for(name)
|
277
|
+
klass.new(value, charset)
|
278
|
+
else
|
279
|
+
OptionalField.new(name, value, charset)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def field_class_for(name)
|
284
|
+
FIELDS_MAP[name.to_s.downcase]
|
225
285
|
end
|
226
286
|
|
227
287
|
# 2.2.3. Long Header Fields
|
@@ -233,30 +293,7 @@ module Mail
|
|
233
293
|
# treated in its unfolded form for further syntactic and semantic
|
234
294
|
# evaluation.
|
235
295
|
def unfold(string)
|
236
|
-
string.gsub(/
|
296
|
+
string.gsub(/#{Constants::CRLF}(#{Constants::WSP})/m, '\1')
|
237
297
|
end
|
238
|
-
|
239
|
-
def create_field(name, value, charset)
|
240
|
-
value = unfold(value) if value.is_a?(String)
|
241
|
-
|
242
|
-
begin
|
243
|
-
new_field(name, value, charset)
|
244
|
-
rescue Mail::Field::ParseError => e
|
245
|
-
field = Mail::UnstructuredField.new(name, value)
|
246
|
-
field.errors << [name, value, e]
|
247
|
-
field
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
def new_field(name, value, charset)
|
252
|
-
lower_case_name = name.to_s.downcase
|
253
|
-
if field_klass = FIELDS_MAP[lower_case_name]
|
254
|
-
field_klass.new(value, charset)
|
255
|
-
else
|
256
|
-
OptionalField.new(name, value, charset)
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
298
|
end
|
261
|
-
|
262
299
|
end
|
@@ -37,9 +37,9 @@ module Mail
|
|
37
37
|
FIELD_NAME = 'bcc'
|
38
38
|
CAPITALIZED_FIELD = 'Bcc'
|
39
39
|
|
40
|
-
def initialize(value =
|
40
|
+
def initialize(value = nil, charset = 'utf-8')
|
41
41
|
@charset = charset
|
42
|
-
super(CAPITALIZED_FIELD,
|
42
|
+
super(CAPITALIZED_FIELD, value, charset)
|
43
43
|
self
|
44
44
|
end
|
45
45
|
|
data/lib/mail/fields/cc_field.rb
CHANGED