mail 2.6.1 → 2.8.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 +150 -107
- data/lib/mail/attachments_list.rb +13 -10
- data/lib/mail/body.rb +104 -90
- data/lib/mail/check_delivery_params.rb +55 -10
- data/lib/mail/configuration.rb +3 -0
- data/lib/mail/constants.rb +79 -0
- data/lib/mail/elements/address.rb +96 -108
- data/lib/mail/elements/address_list.rb +13 -30
- data/lib/mail/elements/content_disposition_element.rb +10 -16
- data/lib/mail/elements/content_location_element.rb +9 -13
- data/lib/mail/elements/content_transfer_encoding_element.rb +7 -11
- data/lib/mail/elements/content_type_element.rb +17 -23
- data/lib/mail/elements/date_time_element.rb +8 -15
- data/lib/mail/elements/envelope_from_element.rb +23 -23
- data/lib/mail/elements/message_ids_element.rb +22 -15
- data/lib/mail/elements/mime_version_element.rb +8 -15
- data/lib/mail/elements/phrase_list.rb +13 -10
- data/lib/mail/elements/received_element.rb +28 -19
- data/lib/mail/elements.rb +1 -0
- data/lib/mail/encodings/7bit.rb +10 -14
- 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 +20 -0
- data/lib/mail/encodings.rb +102 -92
- data/lib/mail/envelope.rb +12 -14
- data/lib/mail/field.rb +121 -85
- data/lib/mail/field_list.rb +62 -8
- data/lib/mail/fields/bcc_field.rb +42 -48
- data/lib/mail/fields/cc_field.rb +29 -50
- data/lib/mail/fields/comments_field.rb +28 -37
- data/lib/mail/fields/common_address_field.rb +170 -0
- data/lib/mail/fields/common_date_field.rb +58 -0
- data/lib/mail/fields/common_field.rb +77 -0
- data/lib/mail/fields/common_message_id_field.rb +42 -0
- data/lib/mail/fields/content_description_field.rb +8 -14
- data/lib/mail/fields/content_disposition_field.rb +20 -44
- data/lib/mail/fields/content_id_field.rb +25 -51
- data/lib/mail/fields/content_location_field.rb +12 -25
- data/lib/mail/fields/content_transfer_encoding_field.rb +32 -31
- data/lib/mail/fields/content_type_field.rb +51 -80
- data/lib/mail/fields/date_field.rb +24 -52
- data/lib/mail/fields/from_field.rb +29 -50
- data/lib/mail/fields/in_reply_to_field.rb +39 -49
- data/lib/mail/fields/keywords_field.rb +19 -32
- data/lib/mail/fields/message_id_field.rb +26 -71
- data/lib/mail/fields/mime_version_field.rb +20 -30
- data/lib/mail/fields/named_structured_field.rb +11 -0
- data/lib/mail/fields/named_unstructured_field.rb +11 -0
- data/lib/mail/fields/optional_field.rb +10 -7
- data/lib/mail/fields/{common/parameter_hash.rb → parameter_hash.rb} +16 -13
- data/lib/mail/fields/received_field.rb +44 -57
- data/lib/mail/fields/references_field.rb +36 -49
- data/lib/mail/fields/reply_to_field.rb +29 -50
- data/lib/mail/fields/resent_bcc_field.rb +29 -50
- data/lib/mail/fields/resent_cc_field.rb +29 -50
- data/lib/mail/fields/resent_date_field.rb +6 -30
- data/lib/mail/fields/resent_from_field.rb +29 -50
- data/lib/mail/fields/resent_message_id_field.rb +6 -29
- data/lib/mail/fields/resent_sender_field.rb +28 -57
- data/lib/mail/fields/resent_to_field.rb +29 -50
- data/lib/mail/fields/return_path_field.rb +51 -55
- data/lib/mail/fields/sender_field.rb +35 -56
- data/lib/mail/fields/structured_field.rb +4 -30
- data/lib/mail/fields/subject_field.rb +10 -11
- data/lib/mail/fields/to_field.rb +29 -50
- data/lib/mail/fields/unstructured_field.rb +36 -50
- data/lib/mail/fields.rb +1 -0
- data/lib/mail/header.rb +73 -110
- data/lib/mail/indifferent_hash.rb +1 -0
- data/lib/mail/mail.rb +6 -11
- data/lib/mail/matchers/attachment_matchers.rb +44 -0
- data/lib/mail/matchers/has_sent_mail.rb +53 -9
- data/lib/mail/message.rb +132 -136
- data/lib/mail/multibyte/chars.rb +24 -180
- data/lib/mail/multibyte/unicode.rb +31 -26
- data/lib/mail/multibyte/utils.rb +27 -43
- data/lib/mail/multibyte.rb +56 -16
- data/lib/mail/network/delivery_methods/exim.rb +9 -11
- data/lib/mail/network/delivery_methods/file_delivery.rb +14 -16
- data/lib/mail/network/delivery_methods/logger_delivery.rb +34 -0
- data/lib/mail/network/delivery_methods/sendmail.rb +68 -24
- data/lib/mail/network/delivery_methods/smtp.rb +77 -54
- data/lib/mail/network/delivery_methods/smtp_connection.rb +5 -9
- data/lib/mail/network/delivery_methods/test_mailer.rb +9 -9
- data/lib/mail/network/retriever_methods/base.rb +9 -8
- data/lib/mail/network/retriever_methods/imap.rb +21 -7
- data/lib/mail/network/retriever_methods/pop3.rb +6 -3
- data/lib/mail/network/retriever_methods/test_retriever.rb +4 -2
- data/lib/mail/network.rb +2 -0
- data/lib/mail/parser_tools.rb +15 -0
- data/lib/mail/parsers/address_lists_parser.rb +33226 -116
- data/lib/mail/parsers/address_lists_parser.rl +179 -0
- data/lib/mail/parsers/content_disposition_parser.rb +883 -49
- data/lib/mail/parsers/content_disposition_parser.rl +89 -0
- data/lib/mail/parsers/content_location_parser.rb +810 -23
- data/lib/mail/parsers/content_location_parser.rl +78 -0
- data/lib/mail/parsers/content_transfer_encoding_parser.rb +510 -21
- data/lib/mail/parsers/content_transfer_encoding_parser.rl +71 -0
- data/lib/mail/parsers/content_type_parser.rb +1031 -47
- data/lib/mail/parsers/content_type_parser.rl +90 -0
- data/lib/mail/parsers/date_time_parser.rb +879 -24
- data/lib/mail/parsers/date_time_parser.rl +69 -0
- data/lib/mail/parsers/envelope_from_parser.rb +3670 -40
- data/lib/mail/parsers/envelope_from_parser.rl +89 -0
- data/lib/mail/parsers/message_ids_parser.rb +5147 -25
- data/lib/mail/parsers/message_ids_parser.rl +93 -0
- data/lib/mail/parsers/mime_version_parser.rb +498 -26
- data/lib/mail/parsers/mime_version_parser.rl +68 -0
- data/lib/mail/parsers/phrase_lists_parser.rb +872 -21
- data/lib/mail/parsers/phrase_lists_parser.rl +90 -0
- data/lib/mail/parsers/received_parser.rb +8777 -42
- data/lib/mail/parsers/received_parser.rl +91 -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 +74 -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 +12 -25
- data/lib/mail/part.rb +11 -12
- data/lib/mail/parts_list.rb +88 -14
- data/lib/mail/smtp_envelope.rb +57 -0
- data/lib/mail/utilities.rb +377 -40
- data/lib/mail/values/unicode_tables.dat +0 -0
- data/lib/mail/version.rb +8 -15
- data/lib/mail/yaml.rb +30 -0
- data/lib/mail.rb +9 -32
- metadata +138 -94
- data/CHANGELOG.rdoc +0 -752
- data/CONTRIBUTING.md +0 -60
- data/Dependencies.txt +0 -2
- data/Gemfile +0 -15
- data/Rakefile +0 -29
- data/TODO.rdoc +0 -9
- data/VERSION +0 -4
- data/lib/mail/core_extensions/nil.rb +0 -19
- data/lib/mail/core_extensions/object.rb +0 -13
- data/lib/mail/core_extensions/smtp.rb +0 -24
- data/lib/mail/core_extensions/string/access.rb +0 -145
- data/lib/mail/core_extensions/string/multibyte.rb +0 -78
- data/lib/mail/core_extensions/string.rb +0 -43
- data/lib/mail/fields/common/address_container.rb +0 -16
- data/lib/mail/fields/common/common_address.rb +0 -135
- data/lib/mail/fields/common/common_date.rb +0 -35
- data/lib/mail/fields/common/common_field.rb +0 -57
- data/lib/mail/fields/common/common_message_id.rb +0 -48
- data/lib/mail/multibyte/exceptions.rb +0 -8
- data/lib/mail/parsers/ragel/common.rl +0 -184
- 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 -2129
- 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 -39
- data/lib/mail/parsers/ragel.rb +0 -17
- data/lib/mail/patterns.rb +0 -37
- data/lib/mail/version_specific/ruby_1_8.rb +0 -119
- data/lib/mail/version_specific/ruby_1_9.rb +0 -159
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,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
|
-
|
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
|
+
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
|
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
|
-
|
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
|
-
|
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
|
95
|
-
#
|
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,
|
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 =~
|
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.
|
123
|
-
line.
|
124
|
-
|
125
|
-
|
126
|
-
|
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.
|
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
|
-
|
156
|
-
|
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
|
-
|
180
|
-
|
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
|
-
|
188
|
-
# Encode
|
189
|
-
address = address.gsub(/("
|
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(
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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 =
|
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
|
-
|
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,53 +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
|
-
|
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.
|
273
|
-
|
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
|
-
#
|
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
|
-
|
260
|
+
last_encoded = nil # Track whether to preserve or drop whitespace
|
289
261
|
|
290
|
-
lines
|
291
|
-
|
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
|
-
|
294
|
-
|
269
|
+
results << encoded
|
270
|
+
else
|
271
|
+
results << unencoded
|
295
272
|
end
|
296
|
-
|
297
|
-
previous_encoding = encoding
|
298
|
-
results << line
|
299
273
|
end
|
300
274
|
|
301
275
|
results
|
302
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
|
303
313
|
end
|
304
314
|
end
|
data/lib/mail/envelope.rb
CHANGED
@@ -1,30 +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 <
|
12
|
-
|
13
|
-
|
14
|
-
super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
|
15
|
-
end
|
16
|
-
|
13
|
+
class Envelope < NamedStructuredField
|
14
|
+
NAME = 'Envelope-From'
|
15
|
+
|
17
16
|
def element
|
18
17
|
@element ||= Mail::EnvelopeFromElement.new(value)
|
19
18
|
end
|
20
|
-
|
21
|
-
def date
|
22
|
-
::DateTime.parse("#{element.date_time}")
|
23
|
-
end
|
24
19
|
|
25
20
|
def from
|
26
21
|
element.address
|
27
22
|
end
|
28
|
-
|
23
|
+
|
24
|
+
def date
|
25
|
+
element.date_time
|
26
|
+
end
|
29
27
|
end
|
30
28
|
end
|
data/lib/mail/field.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'mail/fields'
|
3
|
+
require 'mail/constants'
|
2
4
|
|
3
5
|
# encoding: utf-8
|
4
6
|
module Mail
|
@@ -21,8 +23,6 @@ module Mail
|
|
21
23
|
# sections 3 and 4 of this standard.
|
22
24
|
#
|
23
25
|
class Field
|
24
|
-
|
25
|
-
include Utilities
|
26
26
|
include Comparable
|
27
27
|
|
28
28
|
STRUCTURED_FIELDS = %w[ bcc cc content-description content-disposition
|
@@ -68,7 +68,7 @@ module Mail
|
|
68
68
|
}
|
69
69
|
|
70
70
|
FIELD_NAME_MAP = FIELDS_MAP.inject({}) do |map, (field, field_klass)|
|
71
|
-
map.update(field => field_klass::
|
71
|
+
map.update(field => field_klass::NAME)
|
72
72
|
end
|
73
73
|
|
74
74
|
# Generic Field Exception
|
@@ -82,9 +82,31 @@ module Mail
|
|
82
82
|
|
83
83
|
def initialize(element, value, reason)
|
84
84
|
@element = element
|
85
|
-
@value = value
|
86
|
-
@reason = reason
|
87
|
-
super("#{element} can not parse |#{value}
|
85
|
+
@value = to_utf8(value)
|
86
|
+
@reason = to_utf8(reason)
|
87
|
+
super("#{@element} can not parse |#{@value}|: #{@reason}")
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def to_utf8(text)
|
92
|
+
if text.respond_to?(:force_encoding)
|
93
|
+
text.dup.force_encoding(Encoding::UTF_8)
|
94
|
+
else
|
95
|
+
text
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class NilParseError < ParseError #:nodoc:
|
101
|
+
def initialize(element)
|
102
|
+
super element, nil, 'nil is invalid'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class IncompleteParseError < ParseError #:nodoc:
|
107
|
+
def initialize(element, original_text, unparsed_index)
|
108
|
+
parsed_text = to_utf8(original_text[0...unparsed_index])
|
109
|
+
super element, original_text, "Only able to parse up to #{parsed_text.inspect}"
|
88
110
|
end
|
89
111
|
end
|
90
112
|
|
@@ -92,53 +114,77 @@ module Mail
|
|
92
114
|
class SyntaxError < FieldError #:nodoc:
|
93
115
|
end
|
94
116
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
117
|
+
class << self
|
118
|
+
# Parse a field from a raw header line:
|
119
|
+
#
|
120
|
+
# Mail::Field.parse("field-name: field data")
|
121
|
+
# # => #<Mail::Field …>
|
122
|
+
def parse(field, charset = 'utf-8')
|
123
|
+
name, value = split(field)
|
124
|
+
if name && value
|
125
|
+
new name, value, charset
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def split(raw_field) #:nodoc:
|
130
|
+
if raw_field.index(Constants::COLON)
|
131
|
+
name, value = raw_field.split(Constants::COLON, 2)
|
132
|
+
name.rstrip!
|
133
|
+
if name =~ /\A#{Constants::FIELD_NAME}\z/
|
134
|
+
[ name.rstrip, value.strip ]
|
135
|
+
else
|
136
|
+
Kernel.warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: invalid header name syntax: #{name.inspect}"
|
137
|
+
nil
|
138
|
+
end
|
139
|
+
else
|
140
|
+
raw_field.strip
|
141
|
+
end
|
142
|
+
rescue => error
|
143
|
+
warn "WARNING: Ignoring unparsable header #{raw_field.inspect}: #{error.class}: #{error.message}"
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
|
147
|
+
def field_class_for(name) #:nodoc:
|
148
|
+
FIELDS_MAP[name.to_s.downcase]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
attr_reader :unparsed_value
|
153
|
+
|
154
|
+
# Create a field by name and optional value:
|
102
155
|
#
|
103
|
-
#
|
156
|
+
# Mail::Field.new("field-name", "value")
|
157
|
+
# # => #<Mail::Field …>
|
104
158
|
#
|
105
|
-
#
|
159
|
+
# Values that aren't strings or arrays are coerced to Strings with `#to_s`.
|
106
160
|
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
# it will be passed through as is, for example, content-type
|
110
|
-
# field can accept an array with the type and a hash of
|
111
|
-
# parameters:
|
161
|
+
# Mail::Field.new("field-name", 1234)
|
162
|
+
# # => #<Mail::Field …>
|
112
163
|
#
|
113
|
-
# Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
|
164
|
+
# Mail::Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
|
165
|
+
# # => #<Mail::Field …>
|
114
166
|
def initialize(name, value = nil, charset = 'utf-8')
|
115
167
|
case
|
116
|
-
when name
|
117
|
-
|
118
|
-
|
119
|
-
@raw_value = name
|
120
|
-
@value = nil
|
121
|
-
when name !~ /:/ && value.blank? # Field.new("field-name")
|
168
|
+
when name.index(Constants::COLON)
|
169
|
+
raise ArgumentError, 'Passing an unparsed header field to Mail::Field.new is not supported in Mail 2.8.0+. Use Mail::Field.parse instead.'
|
170
|
+
when Utilities.blank?(value)
|
122
171
|
@name = name
|
123
|
-
@
|
124
|
-
@raw_value = nil
|
172
|
+
@unparsed_value = nil
|
125
173
|
@charset = charset
|
126
|
-
else
|
174
|
+
else
|
127
175
|
@name = name
|
128
|
-
@
|
129
|
-
@raw_value = nil
|
176
|
+
@unparsed_value = value
|
130
177
|
@charset = charset
|
131
178
|
end
|
132
179
|
@name = FIELD_NAME_MAP[@name.to_s.downcase] || @name
|
133
180
|
end
|
134
181
|
|
135
|
-
def field=(
|
136
|
-
@field =
|
182
|
+
def field=(field)
|
183
|
+
@field = field
|
137
184
|
end
|
138
185
|
|
139
186
|
def field
|
140
|
-
|
141
|
-
@field ||= create_field(@name, @value, @charset)
|
187
|
+
@field ||= create_field(@name, @unparsed_value, @charset)
|
142
188
|
end
|
143
189
|
|
144
190
|
def name
|
@@ -163,50 +209,63 @@ module Mail
|
|
163
209
|
end.join(" ")}>"
|
164
210
|
end
|
165
211
|
|
166
|
-
def
|
167
|
-
|
212
|
+
def same(other)
|
213
|
+
other.kind_of?(self.class) && Utilities.match_to_s(other.name, name)
|
168
214
|
end
|
169
215
|
|
170
|
-
def
|
171
|
-
match_to_s(other.
|
216
|
+
def ==(other)
|
217
|
+
same(other) && Utilities.match_to_s(other.value, value)
|
172
218
|
end
|
173
219
|
|
174
|
-
def responsible_for?(
|
175
|
-
name.to_s.casecmp(
|
220
|
+
def responsible_for?(field_name)
|
221
|
+
name.to_s.casecmp(field_name.to_s) == 0
|
176
222
|
end
|
177
223
|
|
178
|
-
|
179
|
-
|
180
|
-
def <=>( other )
|
181
|
-
self.field_order_id <=> other.field_order_id
|
224
|
+
def <=>(other)
|
225
|
+
field_order_id <=> other.field_order_id
|
182
226
|
end
|
183
227
|
|
184
228
|
def field_order_id
|
185
|
-
@field_order_id ||= (
|
229
|
+
@field_order_id ||= FIELD_ORDER_LOOKUP.fetch(self.name.to_s.downcase, 100)
|
186
230
|
end
|
187
231
|
|
188
232
|
def method_missing(name, *args, &block)
|
189
233
|
field.send(name, *args, &block)
|
190
234
|
end
|
191
235
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
date from sender reply-to to cc bcc
|
196
|
-
message-id in-reply-to references
|
197
|
-
subject comments keywords
|
198
|
-
mime-version content-type content-transfer-encoding
|
199
|
-
content-location content-disposition content-description ]
|
236
|
+
def respond_to_missing?(method_name, include_private)
|
237
|
+
field.respond_to?(method_name, include_private) || super
|
238
|
+
end
|
200
239
|
|
201
|
-
FIELD_ORDER_LOOKUP = Hash[
|
240
|
+
FIELD_ORDER_LOOKUP = Hash[%w[
|
241
|
+
return-path received
|
242
|
+
resent-date resent-from resent-sender resent-to
|
243
|
+
resent-cc resent-bcc resent-message-id
|
244
|
+
date from sender reply-to to cc bcc
|
245
|
+
message-id in-reply-to references
|
246
|
+
subject comments keywords
|
247
|
+
mime-version content-type content-transfer-encoding
|
248
|
+
content-location content-disposition content-description
|
249
|
+
].each_with_index.to_a]
|
202
250
|
|
203
251
|
private
|
204
252
|
|
205
|
-
def
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
253
|
+
def create_field(name, value, charset)
|
254
|
+
parse_field(name, value, charset)
|
255
|
+
rescue Mail::Field::ParseError => e
|
256
|
+
field = Mail::UnstructuredField.new(name, value)
|
257
|
+
field.errors << [name, value, e]
|
258
|
+
field
|
259
|
+
end
|
260
|
+
|
261
|
+
def parse_field(name, value, charset)
|
262
|
+
value = unfold(value) if value.is_a?(String)
|
263
|
+
|
264
|
+
if klass = self.class.field_class_for(name)
|
265
|
+
klass.parse(value, charset)
|
266
|
+
else
|
267
|
+
OptionalField.parse(name, value, charset)
|
268
|
+
end
|
210
269
|
end
|
211
270
|
|
212
271
|
# 2.2.3. Long Header Fields
|
@@ -218,30 +277,7 @@ module Mail
|
|
218
277
|
# treated in its unfolded form for further syntactic and semantic
|
219
278
|
# evaluation.
|
220
279
|
def unfold(string)
|
221
|
-
string.gsub(
|
222
|
-
end
|
223
|
-
|
224
|
-
def create_field(name, value, charset)
|
225
|
-
value = unfold(value) if value.is_a?(String)
|
226
|
-
|
227
|
-
begin
|
228
|
-
new_field(name, value, charset)
|
229
|
-
rescue Mail::Field::ParseError => e
|
230
|
-
field = Mail::UnstructuredField.new(name, value)
|
231
|
-
field.errors << [name, value, e]
|
232
|
-
field
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
def new_field(name, value, charset)
|
237
|
-
lower_case_name = name.to_s.downcase
|
238
|
-
if field_klass = FIELDS_MAP[lower_case_name]
|
239
|
-
field_klass.new(value, charset)
|
240
|
-
else
|
241
|
-
OptionalField.new(name, value, charset)
|
242
|
-
end
|
280
|
+
string.gsub(Constants::UNFOLD_WS, '\1')
|
243
281
|
end
|
244
|
-
|
245
282
|
end
|
246
|
-
|
247
283
|
end
|