mail 2.6.3 → 2.7.0
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 +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +91 -79
- data/lib/mail/attachments_list.rb +11 -5
- data/lib/mail/body.rb +46 -39
- data/lib/mail/check_delivery_params.rb +50 -10
- data/lib/mail/configuration.rb +3 -0
- data/lib/mail/constants.rb +4 -2
- data/lib/mail/core_extensions/smtp.rb +20 -16
- data/lib/mail/core_extensions/string.rb +1 -30
- data/lib/mail/elements/address.rb +43 -32
- data/lib/mail/elements/address_list.rb +11 -18
- data/lib/mail/elements/content_disposition_element.rb +9 -15
- data/lib/mail/elements/content_location_element.rb +8 -12
- data/lib/mail/elements/content_transfer_encoding_element.rb +6 -10
- data/lib/mail/elements/content_type_element.rb +9 -19
- data/lib/mail/elements/date_time_element.rb +7 -14
- data/lib/mail/elements/envelope_from_element.rb +15 -21
- data/lib/mail/elements/message_ids_element.rb +12 -14
- data/lib/mail/elements/mime_version_element.rb +7 -14
- data/lib/mail/elements/phrase_list.rb +7 -9
- data/lib/mail/elements/received_element.rb +10 -15
- data/lib/mail/elements.rb +1 -0
- data/lib/mail/encodings/7bit.rb +6 -15
- data/lib/mail/encodings/8bit.rb +5 -18
- data/lib/mail/encodings/base64.rb +15 -10
- data/lib/mail/encodings/binary.rb +4 -22
- data/lib/mail/encodings/identity.rb +24 -0
- data/lib/mail/encodings/quoted_printable.rb +13 -7
- data/lib/mail/encodings/transfer_encoding.rb +47 -28
- data/lib/mail/encodings/unix_to_unix.rb +3 -1
- data/lib/mail/encodings.rb +114 -60
- data/lib/mail/envelope.rb +2 -1
- data/lib/mail/field.rb +112 -62
- data/lib/mail/field_list.rb +1 -0
- data/lib/mail/fields/bcc_field.rb +17 -5
- data/lib/mail/fields/cc_field.rb +2 -2
- data/lib/mail/fields/comments_field.rb +2 -1
- data/lib/mail/fields/common/address_container.rb +3 -2
- data/lib/mail/fields/common/common_address.rb +40 -14
- data/lib/mail/fields/common/common_date.rb +2 -1
- data/lib/mail/fields/common/common_field.rb +5 -11
- data/lib/mail/fields/common/common_message_id.rb +3 -2
- data/lib/mail/fields/common/parameter_hash.rb +2 -1
- data/lib/mail/fields/content_description_field.rb +2 -1
- data/lib/mail/fields/content_disposition_field.rb +14 -13
- data/lib/mail/fields/content_id_field.rb +5 -4
- data/lib/mail/fields/content_location_field.rb +3 -2
- data/lib/mail/fields/content_transfer_encoding_field.rb +3 -2
- data/lib/mail/fields/content_type_field.rb +6 -10
- data/lib/mail/fields/date_field.rb +4 -4
- data/lib/mail/fields/from_field.rb +2 -2
- data/lib/mail/fields/in_reply_to_field.rb +2 -1
- data/lib/mail/fields/keywords_field.rb +3 -3
- data/lib/mail/fields/message_id_field.rb +3 -2
- data/lib/mail/fields/mime_version_field.rb +4 -3
- data/lib/mail/fields/optional_field.rb +5 -1
- data/lib/mail/fields/received_field.rb +5 -4
- data/lib/mail/fields/references_field.rb +2 -1
- data/lib/mail/fields/reply_to_field.rb +2 -2
- data/lib/mail/fields/resent_bcc_field.rb +2 -2
- data/lib/mail/fields/resent_cc_field.rb +2 -2
- data/lib/mail/fields/resent_date_field.rb +2 -2
- data/lib/mail/fields/resent_from_field.rb +2 -2
- data/lib/mail/fields/resent_message_id_field.rb +2 -1
- data/lib/mail/fields/resent_sender_field.rb +2 -2
- data/lib/mail/fields/resent_to_field.rb +2 -2
- data/lib/mail/fields/return_path_field.rb +2 -2
- data/lib/mail/fields/sender_field.rb +2 -2
- data/lib/mail/fields/structured_field.rb +1 -0
- data/lib/mail/fields/subject_field.rb +2 -1
- data/lib/mail/fields/to_field.rb +2 -2
- data/lib/mail/fields/unstructured_field.rb +25 -7
- data/lib/mail/fields.rb +1 -0
- data/lib/mail/header.rb +15 -12
- data/lib/mail/indifferent_hash.rb +1 -0
- data/lib/mail/mail.rb +3 -10
- data/lib/mail/matchers/attachment_matchers.rb +29 -0
- data/lib/mail/matchers/has_sent_mail.rb +51 -7
- data/lib/mail/message.rb +91 -85
- data/lib/mail/multibyte/chars.rb +32 -30
- data/lib/mail/multibyte/unicode.rb +21 -16
- data/lib/mail/multibyte/utils.rb +1 -0
- data/lib/mail/multibyte.rb +65 -15
- data/lib/mail/network/delivery_methods/exim.rb +7 -10
- data/lib/mail/network/delivery_methods/file_delivery.rb +5 -8
- data/lib/mail/network/delivery_methods/logger_delivery.rb +37 -0
- data/lib/mail/network/delivery_methods/sendmail.rb +17 -11
- data/lib/mail/network/delivery_methods/smtp.rb +60 -53
- data/lib/mail/network/delivery_methods/smtp_connection.rb +11 -6
- data/lib/mail/network/delivery_methods/test_mailer.rb +6 -8
- data/lib/mail/network/retriever_methods/base.rb +1 -0
- data/lib/mail/network/retriever_methods/imap.rb +19 -5
- data/lib/mail/network/retriever_methods/pop3.rb +4 -1
- data/lib/mail/network/retriever_methods/test_retriever.rb +2 -1
- data/lib/mail/network.rb +2 -0
- data/lib/mail/parser_tools.rb +15 -0
- data/lib/mail/parsers/address_lists_parser.rb +33187 -112
- data/lib/mail/parsers/address_lists_parser.rl +172 -0
- data/lib/mail/parsers/content_disposition_parser.rb +889 -55
- data/lib/mail/parsers/content_disposition_parser.rl +82 -0
- data/lib/mail/parsers/content_location_parser.rb +801 -23
- data/lib/mail/parsers/content_location_parser.rl +71 -0
- data/lib/mail/parsers/content_transfer_encoding_parser.rb +511 -22
- data/lib/mail/parsers/content_transfer_encoding_parser.rl +64 -0
- data/lib/mail/parsers/content_type_parser.rb +1033 -54
- data/lib/mail/parsers/content_type_parser.rl +83 -0
- data/lib/mail/parsers/date_time_parser.rb +875 -25
- data/lib/mail/parsers/date_time_parser.rl +62 -0
- data/lib/mail/parsers/envelope_from_parser.rb +3565 -36
- data/lib/mail/parsers/envelope_from_parser.rl +82 -0
- data/lib/mail/parsers/message_ids_parser.rb +2844 -25
- data/lib/mail/parsers/message_ids_parser.rl +82 -0
- data/lib/mail/parsers/mime_version_parser.rb +494 -26
- data/lib/mail/parsers/mime_version_parser.rl +61 -0
- data/lib/mail/parsers/phrase_lists_parser.rb +876 -18
- data/lib/mail/parsers/phrase_lists_parser.rl +83 -0
- data/lib/mail/parsers/received_parser.rb +8744 -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 +17 -24
- data/lib/mail/part.rb +6 -3
- data/lib/mail/parts_list.rb +31 -14
- data/lib/mail/utilities.rb +107 -8
- data/lib/mail/values/unicode_tables.dat +0 -0
- data/lib/mail/version.rb +3 -2
- data/lib/mail/version_specific/ruby_1_8.rb +50 -6
- data/lib/mail/version_specific/ruby_1_9.rb +103 -18
- data/lib/mail.rb +5 -12
- metadata +33 -57
- data/CHANGELOG.rdoc +0 -759
- data/CONTRIBUTING.md +0 -60
- data/Dependencies.txt +0 -2
- data/Gemfile +0 -15
- data/Rakefile +0 -29
- data/TODO.rdoc +0 -9
- data/lib/mail/core_extensions/nil.rb +0 -19
- data/lib/mail/core_extensions/object.rb +0 -13
- data/lib/mail/core_extensions/string/access.rb +0 -145
- data/lib/mail/core_extensions/string/multibyte.rb +0 -78
- data/lib/mail/multibyte/exceptions.rb +0 -8
- 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 -17
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
require 'mail/parsers/received_parser'
|
|
4
|
+
require 'date'
|
|
5
|
+
|
|
2
6
|
module Mail
|
|
3
7
|
class ReceivedElement
|
|
4
|
-
|
|
5
8
|
include Mail::Utilities
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
attr_reader :date_time, :info
|
|
10
|
+
|
|
11
|
+
def initialize(string)
|
|
12
|
+
received = Mail::Parsers::ReceivedParser.parse(string)
|
|
9
13
|
@date_time = ::DateTime.parse("#{received.date} #{received.time}")
|
|
10
14
|
@info = received.info
|
|
11
15
|
end
|
|
12
|
-
|
|
13
|
-
def date_time
|
|
14
|
-
@date_time
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def info
|
|
18
|
-
@info
|
|
19
|
-
end
|
|
20
|
-
|
|
16
|
+
|
|
21
17
|
def to_s(*args)
|
|
22
|
-
"#{
|
|
18
|
+
"#{info}; #{date_time.to_s(*args)}"
|
|
23
19
|
end
|
|
24
|
-
|
|
25
20
|
end
|
|
26
21
|
end
|
data/lib/mail/elements.rb
CHANGED
data/lib/mail/encodings/7bit.rb
CHANGED
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
require 'mail/encodings/8bit'
|
|
3
4
|
|
|
4
5
|
module Mail
|
|
5
6
|
module Encodings
|
|
7
|
+
# 7bit and 8bit are equivalent. 7bit encoding is for text only.
|
|
6
8
|
class SevenBit < EightBit
|
|
7
9
|
NAME = '7bit'
|
|
8
|
-
|
|
9
10
|
PRIORITY = 1
|
|
11
|
+
Encodings.register(NAME, self)
|
|
10
12
|
|
|
11
|
-
# 7bit and 8bit operate the same
|
|
12
|
-
|
|
13
|
-
# Decode the string
|
|
14
13
|
def self.decode(str)
|
|
15
|
-
|
|
14
|
+
::Mail::Utilities.binary_unsafe_to_lf str
|
|
16
15
|
end
|
|
17
|
-
|
|
18
|
-
# Encode the string
|
|
16
|
+
|
|
19
17
|
def self.encode(str)
|
|
20
|
-
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Idenity encodings have a fixed cost, 1 byte out per 1 byte in
|
|
24
|
-
def self.cost(str)
|
|
25
|
-
super
|
|
18
|
+
::Mail::Utilities.binary_unsafe_to_crlf str
|
|
26
19
|
end
|
|
27
|
-
|
|
28
|
-
Encodings.register(NAME, self)
|
|
29
20
|
end
|
|
30
21
|
end
|
|
31
22
|
end
|
data/lib/mail/encodings/8bit.rb
CHANGED
|
@@ -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
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
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(
|
|
19
|
+
RubyVer.decode_base64(str)
|
|
18
20
|
end
|
|
19
|
-
|
|
20
|
-
# Encode the string to Base64
|
|
21
|
+
|
|
21
22
|
def self.encode(str)
|
|
22
|
-
RubyVer.encode_base64(
|
|
23
|
+
::Mail::Utilities.binary_unsafe_to_crlf(RubyVer.encode_base64(str))
|
|
23
24
|
end
|
|
24
25
|
|
|
25
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
require 'mail/encodings/identity'
|
|
3
4
|
|
|
4
5
|
module Mail
|
|
5
6
|
module Encodings
|
|
6
|
-
class Binary <
|
|
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?(
|
|
12
|
-
EightBit.can_encode?
|
|
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
|
|
19
|
+
str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def self.encode(str)
|
|
22
|
-
[str
|
|
23
|
+
[str].pack("M")
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def self.cost(str)
|
|
26
27
|
# These bytes probably do not need encoding
|
|
27
28
|
c = str.count("\x9\xA\xD\x20-\x3C\x3E-\x7E")
|
|
28
|
-
# Everything else turns into =XX where XX is a
|
|
29
|
+
# Everything else turns into =XX where XX is a
|
|
29
30
|
# two digit hex number (taking 3 bytes)
|
|
30
31
|
total = (str.bytesize - c)*3 + c
|
|
31
32
|
total.to_f/str.bytesize
|
|
32
33
|
end
|
|
33
|
-
|
|
34
|
+
|
|
35
|
+
# QP inserts newlines automatically and cannot violate the SMTP spec.
|
|
36
|
+
def self.compatible_input?(str)
|
|
37
|
+
true
|
|
38
|
+
end
|
|
39
|
+
|
|
34
40
|
private
|
|
35
41
|
|
|
36
42
|
Encodings.register(NAME, self)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
module Mail
|
|
3
4
|
module Encodings
|
|
4
5
|
class TransferEncoding
|
|
@@ -6,52 +7,70 @@ module Mail
|
|
|
6
7
|
|
|
7
8
|
PRIORITY = -1
|
|
8
9
|
|
|
10
|
+
# And encoding's superclass can always transport it since the
|
|
11
|
+
# class hierarchy is arranged e.g. Base64 < 7bit < 8bit < Binary.
|
|
9
12
|
def self.can_transport?(enc)
|
|
10
|
-
enc
|
|
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.
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
def self.negotiate(message_encoding, source_encoding, str, allowed_encodings = nil)
|
|
36
|
+
message_encoding = Encodings.get_encoding(message_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
|
-
|
|
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
|
|
54
|
-
|
|
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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Mail
|
|
2
3
|
module Encodings
|
|
3
|
-
|
|
4
|
+
class UnixToUnix < TransferEncoding
|
|
4
5
|
NAME = "x-uuencode"
|
|
5
6
|
|
|
6
7
|
def self.decode(str)
|
|
@@ -12,6 +13,7 @@ module Mail
|
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
Encodings.register(NAME, self)
|
|
16
|
+
Encodings.register("uuencode", self)
|
|
15
17
|
end
|
|
16
18
|
end
|
|
17
19
|
end
|
data/lib/mail/encodings.rb
CHANGED
|
@@ -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,7 +7,6 @@ module Mail
|
|
|
6
7
|
end
|
|
7
8
|
|
|
8
9
|
module Encodings
|
|
9
|
-
|
|
10
10
|
include Mail::Constants
|
|
11
11
|
extend Mail::Utilities
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ module Mail
|
|
|
18
18
|
#
|
|
19
19
|
# Encodings.register "base64", Mail::Encodings::Base64
|
|
20
20
|
def Encodings.register(name, cls)
|
|
21
|
-
|
|
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?(
|
|
30
|
-
@transfer_encodings.include? get_name(
|
|
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(
|
|
42
|
-
@transfer_encodings[get_name(
|
|
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(
|
|
50
|
-
|
|
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
|
+
RubyVer.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
|
|
@@ -121,7 +129,7 @@ module Mail
|
|
|
121
129
|
# Split on white-space boundaries with capture, so we capture the white-space as well
|
|
122
130
|
lines.each do |line|
|
|
123
131
|
line.gsub!(ENCODED_VALUE) do |string|
|
|
124
|
-
case $
|
|
132
|
+
case $2
|
|
125
133
|
when *B_VALUES then b_value_decode(string)
|
|
126
134
|
when *Q_VALUES then q_value_decode(string)
|
|
127
135
|
end
|
|
@@ -159,32 +167,51 @@ module Mail
|
|
|
159
167
|
|
|
160
168
|
def Encodings.address_encode(address, charset = 'utf-8')
|
|
161
169
|
if address.is_a?(Array)
|
|
162
|
-
# loop back through for each element
|
|
163
170
|
address.compact.map { |a| Encodings.address_encode(a, charset) }.join(", ")
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
encode_non_usascii(address, charset) if address
|
|
171
|
+
elsif address
|
|
172
|
+
encode_non_usascii(address, charset)
|
|
167
173
|
end
|
|
168
174
|
end
|
|
169
175
|
|
|
170
176
|
def Encodings.encode_non_usascii(address, charset)
|
|
171
177
|
return address if address.ascii_only? or charset.nil?
|
|
172
|
-
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if
|
|
183
|
-
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)
|
|
184
196
|
end
|
|
185
|
-
|
|
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
|
|
186
206
|
end
|
|
187
|
-
|
|
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
|
|
188
215
|
end
|
|
189
216
|
|
|
190
217
|
# Encode a string with Base64 Encoding and returns it ready to be inserted
|
|
@@ -194,12 +221,15 @@ module Mail
|
|
|
194
221
|
#
|
|
195
222
|
# Encodings.b_value_encode('This is あ string', 'UTF-8')
|
|
196
223
|
# #=> "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?="
|
|
197
|
-
def Encodings.b_value_encode(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
203
233
|
end
|
|
204
234
|
|
|
205
235
|
# Encode a string with Quoted-Printable Encoding and returns it ready to be inserted
|
|
@@ -240,50 +270,74 @@ module Mail
|
|
|
240
270
|
RubyVer.q_value_decode(str)
|
|
241
271
|
end
|
|
242
272
|
|
|
243
|
-
def Encodings.split_encoding_from_string( str )
|
|
244
|
-
match = str.match(/\=\?([^?]+)?\?[QB]\?(.*)\?\=/mi)
|
|
245
|
-
if match
|
|
246
|
-
match[1]
|
|
247
|
-
else
|
|
248
|
-
nil
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
273
|
def Encodings.find_encoding(str)
|
|
253
274
|
RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
|
|
254
275
|
end
|
|
255
276
|
|
|
256
277
|
# Gets the encoding type (Q or B) from the string.
|
|
257
|
-
def Encodings.
|
|
258
|
-
|
|
259
|
-
if match
|
|
260
|
-
match[1]
|
|
261
|
-
else
|
|
262
|
-
nil
|
|
263
|
-
end
|
|
278
|
+
def Encodings.value_encoding_from_string(str)
|
|
279
|
+
str[ENCODED_VALUE, 1]
|
|
264
280
|
end
|
|
265
281
|
|
|
266
|
-
#
|
|
267
|
-
# encoding (Q or B) can be joined together.
|
|
282
|
+
# Split header line into proper encoded and unencoded parts.
|
|
268
283
|
#
|
|
269
284
|
# String has to be of the format =?<encoding>?[QB]?<string>?=
|
|
285
|
+
#
|
|
286
|
+
# Omit unencoded space after an encoded-word.
|
|
270
287
|
def Encodings.collapse_adjacent_encodings(str)
|
|
271
|
-
lines = str.split(/(\?=)\s*(=\?)/).each_slice(2).map(&:join)
|
|
272
288
|
results = []
|
|
273
|
-
|
|
289
|
+
last_encoded = nil # Track whether to preserve or drop whitespace
|
|
274
290
|
|
|
275
|
-
lines
|
|
276
|
-
|
|
291
|
+
lines = str.split(FULL_ENCODED_VALUE)
|
|
292
|
+
lines.each_slice(2) do |unencoded, encoded|
|
|
293
|
+
if last_encoded = encoded
|
|
294
|
+
if !Utilities.blank?(unencoded) || (!last_encoded && unencoded != EMPTY)
|
|
295
|
+
results << unencoded
|
|
296
|
+
end
|
|
277
297
|
|
|
278
|
-
|
|
279
|
-
|
|
298
|
+
results << encoded
|
|
299
|
+
else
|
|
300
|
+
results << unencoded
|
|
280
301
|
end
|
|
281
|
-
|
|
282
|
-
previous_encoding = encoding
|
|
283
|
-
results << line
|
|
284
302
|
end
|
|
285
303
|
|
|
286
304
|
results
|
|
287
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
|
|
288
342
|
end
|
|
289
343
|
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
|
#
|
|
@@ -11,7 +12,7 @@ module Mail
|
|
|
11
12
|
class Envelope < StructuredField
|
|
12
13
|
|
|
13
14
|
def initialize(*args)
|
|
14
|
-
super(FIELD_NAME,
|
|
15
|
+
super(FIELD_NAME, args.last.to_s)
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def element
|