mail 2.5.5 → 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 +170 -108
- data/lib/mail/attachments_list.rb +13 -10
- data/lib/mail/body.rb +105 -91
- data/lib/mail/check_delivery_params.rb +30 -22
- data/lib/mail/configuration.rb +3 -0
- data/lib/mail/constants.rb +79 -0
- data/lib/mail/elements/address.rb +118 -174
- data/lib/mail/elements/address_list.rb +16 -56
- data/lib/mail/elements/content_disposition_element.rb +12 -22
- data/lib/mail/elements/content_location_element.rb +9 -17
- data/lib/mail/elements/content_transfer_encoding_element.rb +8 -19
- data/lib/mail/elements/content_type_element.rb +20 -30
- data/lib/mail/elements/date_time_element.rb +10 -21
- data/lib/mail/elements/envelope_from_element.rb +23 -31
- data/lib/mail/elements/message_ids_element.rb +22 -20
- data/lib/mail/elements/mime_version_element.rb +10 -21
- data/lib/mail/elements/phrase_list.rb +13 -15
- data/lib/mail/elements/received_element.rb +26 -21
- 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 -93
- data/lib/mail/envelope.rb +12 -19
- data/lib/mail/field.rb +143 -71
- data/lib/mail/field_list.rb +73 -19
- 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 +31 -36
- 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 +43 -51
- data/lib/mail/fields.rb +1 -0
- data/lib/mail/header.rb +78 -129
- data/lib/mail/indifferent_hash.rb +1 -0
- data/lib/mail/mail.rb +18 -11
- data/lib/mail/matchers/attachment_matchers.rb +44 -0
- data/lib/mail/matchers/has_sent_mail.rb +81 -4
- data/lib/mail/message.rb +142 -139
- data/lib/mail/multibyte/chars.rb +24 -180
- data/lib/mail/multibyte/unicode.rb +32 -27
- data/lib/mail/multibyte/utils.rb +27 -43
- data/lib/mail/multibyte.rb +56 -16
- data/lib/mail/network/delivery_methods/exim.rb +6 -4
- data/lib/mail/network/delivery_methods/file_delivery.rb +12 -10
- data/lib/mail/network/delivery_methods/logger_delivery.rb +34 -0
- data/lib/mail/network/delivery_methods/sendmail.rb +63 -21
- data/lib/mail/network/delivery_methods/smtp.rb +76 -50
- data/lib/mail/network/delivery_methods/smtp_connection.rb +4 -4
- data/lib/mail/network/delivery_methods/test_mailer.rb +5 -2
- data/lib/mail/network/retriever_methods/base.rb +9 -8
- data/lib/mail/network/retriever_methods/imap.rb +37 -18
- 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 +33242 -0
- data/lib/mail/parsers/address_lists_parser.rl +179 -0
- data/lib/mail/parsers/content_disposition_parser.rb +901 -0
- data/lib/mail/parsers/content_disposition_parser.rl +89 -0
- data/lib/mail/parsers/content_location_parser.rb +822 -0
- data/lib/mail/parsers/content_location_parser.rl +78 -0
- data/lib/mail/parsers/content_transfer_encoding_parser.rb +522 -0
- data/lib/mail/parsers/content_transfer_encoding_parser.rl +71 -0
- data/lib/mail/parsers/content_type_parser.rb +1048 -0
- data/lib/mail/parsers/content_type_parser.rl +90 -0
- data/lib/mail/parsers/date_time_parser.rb +891 -0
- data/lib/mail/parsers/date_time_parser.rl +69 -0
- data/lib/mail/parsers/envelope_from_parser.rb +3675 -0
- data/lib/mail/parsers/envelope_from_parser.rl +89 -0
- data/lib/mail/parsers/message_ids_parser.rb +5161 -0
- data/lib/mail/parsers/message_ids_parser.rl +93 -0
- data/lib/mail/parsers/mime_version_parser.rb +513 -0
- data/lib/mail/parsers/mime_version_parser.rl +68 -0
- data/lib/mail/parsers/phrase_lists_parser.rb +884 -0
- data/lib/mail/parsers/phrase_lists_parser.rl +90 -0
- data/lib/mail/parsers/received_parser.rb +8782 -0
- 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/rfc5322_date_time.rl +37 -0
- data/lib/mail/parsers/rfc5322_lexical_tokens.rl +60 -0
- data/lib/mail/parsers.rb +13 -0
- data/lib/mail/part.rb +11 -12
- data/lib/mail/parts_list.rb +90 -14
- data/lib/mail/smtp_envelope.rb +57 -0
- data/lib/mail/utilities.rb +415 -76
- 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 +127 -79
- data/CHANGELOG.rdoc +0 -742
- data/CONTRIBUTING.md +0 -45
- data/Dependencies.txt +0 -3
- data/Gemfile +0 -32
- data/Rakefile +0 -21
- data/TODO.rdoc +0 -9
- data/lib/VERSION +0 -4
- data/lib/load_parsers.rb +0 -35
- 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 -33
- data/lib/mail/fields/common/address_container.rb +0 -16
- data/lib/mail/fields/common/common_address.rb +0 -140
- data/lib/mail/fields/common/common_date.rb +0 -42
- 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/address_lists.rb +0 -64
- data/lib/mail/parsers/address_lists.treetop +0 -19
- data/lib/mail/parsers/content_disposition.rb +0 -535
- data/lib/mail/parsers/content_disposition.treetop +0 -46
- data/lib/mail/parsers/content_location.rb +0 -139
- data/lib/mail/parsers/content_location.treetop +0 -20
- data/lib/mail/parsers/content_transfer_encoding.rb +0 -201
- data/lib/mail/parsers/content_transfer_encoding.treetop +0 -18
- data/lib/mail/parsers/content_type.rb +0 -971
- data/lib/mail/parsers/content_type.treetop +0 -68
- data/lib/mail/parsers/date_time.rb +0 -114
- data/lib/mail/parsers/date_time.treetop +0 -11
- data/lib/mail/parsers/envelope_from.rb +0 -194
- data/lib/mail/parsers/envelope_from.treetop +0 -32
- data/lib/mail/parsers/message_ids.rb +0 -45
- data/lib/mail/parsers/message_ids.treetop +0 -15
- data/lib/mail/parsers/mime_version.rb +0 -144
- data/lib/mail/parsers/mime_version.treetop +0 -19
- data/lib/mail/parsers/phrase_lists.rb +0 -45
- data/lib/mail/parsers/phrase_lists.treetop +0 -15
- data/lib/mail/parsers/received.rb +0 -71
- data/lib/mail/parsers/received.treetop +0 -11
- data/lib/mail/parsers/rfc2045.rb +0 -421
- data/lib/mail/parsers/rfc2045.treetop +0 -35
- data/lib/mail/parsers/rfc2822.rb +0 -5397
- data/lib/mail/parsers/rfc2822.treetop +0 -408
- data/lib/mail/parsers/rfc2822_obsolete.rb +0 -3768
- data/lib/mail/parsers/rfc2822_obsolete.treetop +0 -241
- data/lib/mail/patterns.rb +0 -35
- data/lib/mail/version_specific/ruby_1_8.rb +0 -119
- data/lib/mail/version_specific/ruby_1_9.rb +0 -147
- data/lib/tasks/corpus.rake +0 -125
- data/lib/tasks/treetop.rake +0 -10
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mail
|
4
|
+
class SmtpEnvelope #:nodoc:
|
5
|
+
# Reasonable cap on address length to avoid SMTP line length
|
6
|
+
# overflow on old SMTP servers.
|
7
|
+
MAX_ADDRESS_BYTESIZE = 2000
|
8
|
+
|
9
|
+
attr_reader :from, :to, :message
|
10
|
+
|
11
|
+
def initialize(mail)
|
12
|
+
self.from = mail.smtp_envelope_from
|
13
|
+
self.to = mail.smtp_envelope_to
|
14
|
+
self.message = mail.encoded
|
15
|
+
end
|
16
|
+
|
17
|
+
def from=(addr)
|
18
|
+
if Utilities.blank? addr
|
19
|
+
raise ArgumentError, "SMTP From address may not be blank: #{addr.inspect}"
|
20
|
+
end
|
21
|
+
|
22
|
+
@from = validate_addr 'From', addr
|
23
|
+
end
|
24
|
+
|
25
|
+
def to=(addr)
|
26
|
+
if Utilities.blank?(addr)
|
27
|
+
raise ArgumentError, "SMTP To address may not be blank: #{addr.inspect}"
|
28
|
+
end
|
29
|
+
|
30
|
+
@to = Array(addr).map do |addr|
|
31
|
+
validate_addr 'To', addr
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def message=(message)
|
36
|
+
if Utilities.blank?(message)
|
37
|
+
raise ArgumentError, 'SMTP message may not be blank'
|
38
|
+
end
|
39
|
+
|
40
|
+
@message = message
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
private
|
45
|
+
def validate_addr(addr_name, addr)
|
46
|
+
if addr.bytesize > MAX_ADDRESS_BYTESIZE
|
47
|
+
raise ArgumentError, "SMTP #{addr_name} address may not exceed #{MAX_ADDRESS_BYTESIZE} bytes: #{addr.inspect}"
|
48
|
+
end
|
49
|
+
|
50
|
+
if /[\r\n]/ =~ addr
|
51
|
+
raise ArgumentError, "SMTP #{addr_name} address may not contain CR or LF line breaks: #{addr.inspect}"
|
52
|
+
end
|
53
|
+
|
54
|
+
addr
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/mail/utilities.rb
CHANGED
@@ -1,44 +1,58 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
require 'mail/constants'
|
4
|
+
require 'socket'
|
5
|
+
|
2
6
|
module Mail
|
3
7
|
module Utilities
|
4
|
-
|
5
|
-
|
8
|
+
extend self
|
9
|
+
|
6
10
|
# Returns true if the string supplied is free from characters not allowed as an ATOM
|
7
11
|
def atom_safe?( str )
|
8
|
-
not ATOM_UNSAFE === str
|
12
|
+
not Constants::ATOM_UNSAFE === str
|
9
13
|
end
|
10
14
|
|
11
|
-
# If the string supplied has ATOM unsafe characters in it, will return the string quoted
|
15
|
+
# If the string supplied has ATOM unsafe characters in it, will return the string quoted
|
12
16
|
# in double quotes, otherwise returns the string unmodified
|
13
17
|
def quote_atom( str )
|
14
18
|
atom_safe?( str ) ? str : dquote(str)
|
15
19
|
end
|
16
20
|
|
17
|
-
# If the string supplied has PHRASE unsafe characters in it, will return the string quoted
|
21
|
+
# If the string supplied has PHRASE unsafe characters in it, will return the string quoted
|
18
22
|
# in double quotes, otherwise returns the string unmodified
|
19
23
|
def quote_phrase( str )
|
20
|
-
if
|
24
|
+
if str.respond_to?(:force_encoding)
|
21
25
|
original_encoding = str.encoding
|
22
|
-
str.force_encoding('ASCII-8BIT')
|
23
|
-
if
|
24
|
-
dquote(
|
26
|
+
ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
|
27
|
+
if Constants::PHRASE_UNSAFE === ascii_str
|
28
|
+
dquote(ascii_str).force_encoding(original_encoding)
|
25
29
|
else
|
26
|
-
str
|
30
|
+
str
|
27
31
|
end
|
28
32
|
else
|
29
|
-
|
33
|
+
Constants::PHRASE_UNSAFE === str ? dquote(str) : str
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
33
37
|
# Returns true if the string supplied is free from characters not allowed as a TOKEN
|
34
38
|
def token_safe?( str )
|
35
|
-
not TOKEN_UNSAFE === str
|
39
|
+
not Constants::TOKEN_UNSAFE === str
|
36
40
|
end
|
37
41
|
|
38
|
-
# If the string supplied has TOKEN unsafe characters in it, will return the string quoted
|
42
|
+
# If the string supplied has TOKEN unsafe characters in it, will return the string quoted
|
39
43
|
# in double quotes, otherwise returns the string unmodified
|
40
44
|
def quote_token( str )
|
41
|
-
|
45
|
+
if str.respond_to?(:force_encoding)
|
46
|
+
original_encoding = str.encoding
|
47
|
+
ascii_str = str.to_s.dup.force_encoding('ASCII-8BIT')
|
48
|
+
if token_safe?( ascii_str )
|
49
|
+
str
|
50
|
+
else
|
51
|
+
dquote(ascii_str).force_encoding(original_encoding)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
token_safe?( str ) ? str : dquote(str)
|
55
|
+
end
|
42
56
|
end
|
43
57
|
|
44
58
|
# Wraps supplied string in double quotes and applies \-escaping as necessary,
|
@@ -67,157 +81,482 @@ module Mail
|
|
67
81
|
# unqoute(string) #=> 'This is "a string"'
|
68
82
|
def unquote( str )
|
69
83
|
if str =~ /^"(.*?)"$/
|
70
|
-
$1
|
84
|
+
unescape($1)
|
71
85
|
else
|
72
86
|
str
|
73
87
|
end
|
74
88
|
end
|
75
|
-
|
89
|
+
|
90
|
+
# Removes any \-escaping.
|
91
|
+
#
|
92
|
+
# Example:
|
93
|
+
#
|
94
|
+
# string = 'This is \"a string\"'
|
95
|
+
# unescape(string) #=> 'This is "a string"'
|
96
|
+
#
|
97
|
+
# string = '"This is \"a string\""'
|
98
|
+
# unescape(string) #=> '"This is "a string""'
|
99
|
+
def unescape( str )
|
100
|
+
str.gsub(/\\(.)/, '\1')
|
101
|
+
end
|
102
|
+
|
76
103
|
# Wraps a string in parenthesis and escapes any that are in the string itself.
|
77
|
-
#
|
104
|
+
#
|
78
105
|
# Example:
|
79
|
-
#
|
106
|
+
#
|
80
107
|
# paren( 'This is a string' ) #=> '(This is a string)'
|
81
108
|
def paren( str )
|
82
|
-
|
109
|
+
Utilities.paren( str )
|
83
110
|
end
|
84
|
-
|
111
|
+
|
85
112
|
# Unwraps a string from being wrapped in parenthesis
|
86
|
-
#
|
113
|
+
#
|
87
114
|
# Example:
|
88
|
-
#
|
115
|
+
#
|
89
116
|
# str = '(This is a string)'
|
90
117
|
# unparen( str ) #=> 'This is a string'
|
91
118
|
def unparen( str )
|
92
|
-
|
93
|
-
|
119
|
+
if str.start_with?('(') && str.end_with?(')')
|
120
|
+
str.slice(1..-2)
|
121
|
+
else
|
122
|
+
str
|
123
|
+
end
|
94
124
|
end
|
95
|
-
|
125
|
+
|
96
126
|
# Wraps a string in angle brackets and escapes any that are in the string itself
|
97
|
-
#
|
127
|
+
#
|
98
128
|
# Example:
|
99
|
-
#
|
129
|
+
#
|
100
130
|
# bracket( 'This is a string' ) #=> '<This is a string>'
|
101
131
|
def bracket( str )
|
102
|
-
|
132
|
+
Utilities.bracket( str )
|
103
133
|
end
|
104
|
-
|
134
|
+
|
105
135
|
# Unwraps a string from being wrapped in parenthesis
|
106
|
-
#
|
136
|
+
#
|
107
137
|
# Example:
|
108
|
-
#
|
138
|
+
#
|
109
139
|
# str = '<This is a string>'
|
110
140
|
# unbracket( str ) #=> 'This is a string'
|
111
141
|
def unbracket( str )
|
112
|
-
|
113
|
-
|
142
|
+
if str.start_with?('<') && str.end_with?('>')
|
143
|
+
str.slice(1..-2)
|
144
|
+
else
|
145
|
+
str
|
146
|
+
end
|
114
147
|
end
|
115
|
-
|
148
|
+
|
116
149
|
# Escape parenthesies in a string
|
117
|
-
#
|
150
|
+
#
|
118
151
|
# Example:
|
119
|
-
#
|
152
|
+
#
|
120
153
|
# str = 'This is (a) string'
|
121
154
|
# escape_paren( str ) #=> 'This is \(a\) string'
|
122
155
|
def escape_paren( str )
|
123
|
-
|
156
|
+
Utilities.escape_paren( str )
|
124
157
|
end
|
125
|
-
|
158
|
+
|
126
159
|
def uri_escape( str )
|
127
160
|
uri_parser.escape(str)
|
128
161
|
end
|
129
|
-
|
162
|
+
|
130
163
|
def uri_unescape( str )
|
131
164
|
uri_parser.unescape(str)
|
132
165
|
end
|
133
|
-
|
166
|
+
|
134
167
|
def uri_parser
|
135
|
-
@uri_parser ||= URI.const_defined?(:
|
168
|
+
@uri_parser ||= URI.const_defined?(:DEFAULT_PARSER) ? URI::DEFAULT_PARSER : URI
|
136
169
|
end
|
137
|
-
|
170
|
+
|
138
171
|
# Matches two objects with their to_s values case insensitively
|
139
|
-
#
|
172
|
+
#
|
140
173
|
# Example:
|
141
|
-
#
|
174
|
+
#
|
142
175
|
# obj2 = "This_is_An_object"
|
143
176
|
# obj1 = :this_IS_an_object
|
144
177
|
# match_to_s( obj1, obj2 ) #=> true
|
145
178
|
def match_to_s( obj1, obj2 )
|
146
179
|
obj1.to_s.casecmp(obj2.to_s) == 0
|
147
180
|
end
|
148
|
-
|
181
|
+
|
149
182
|
# Capitalizes a string that is joined by hyphens correctly.
|
150
|
-
#
|
183
|
+
#
|
151
184
|
# Example:
|
152
|
-
#
|
185
|
+
#
|
153
186
|
# string = 'resent-from-field'
|
154
187
|
# capitalize_field( string ) #=> 'Resent-From-Field'
|
155
188
|
def capitalize_field( str )
|
156
189
|
str.to_s.split("-").map { |v| v.capitalize }.join("-")
|
157
190
|
end
|
158
|
-
|
191
|
+
|
159
192
|
# Takes an underscored word and turns it into a class name
|
160
|
-
#
|
193
|
+
#
|
161
194
|
# Example:
|
162
|
-
#
|
195
|
+
#
|
163
196
|
# constantize("hello") #=> "Hello"
|
164
197
|
# constantize("hello-there") #=> "HelloThere"
|
165
198
|
# constantize("hello-there-mate") #=> "HelloThereMate"
|
166
199
|
def constantize( str )
|
167
200
|
str.to_s.split(/[-_]/).map { |v| v.capitalize }.to_s
|
168
201
|
end
|
169
|
-
|
202
|
+
|
170
203
|
# Swaps out all underscores (_) for hyphens (-) good for stringing from symbols
|
171
204
|
# a field name.
|
172
|
-
#
|
205
|
+
#
|
173
206
|
# Example:
|
174
|
-
#
|
207
|
+
#
|
175
208
|
# string = :resent_from_field
|
176
|
-
# dasherize
|
209
|
+
# dasherize( string ) #=> 'resent-from-field'
|
177
210
|
def dasherize( str )
|
178
|
-
str.to_s.
|
211
|
+
str.to_s.tr(Constants::UNDERSCORE, Constants::HYPHEN)
|
179
212
|
end
|
180
213
|
|
181
214
|
# Swaps out all hyphens (-) for underscores (_) good for stringing to symbols
|
182
215
|
# a field name.
|
183
|
-
#
|
216
|
+
#
|
184
217
|
# Example:
|
185
|
-
#
|
218
|
+
#
|
186
219
|
# string = :resent_from_field
|
187
220
|
# underscoreize ( string ) #=> 'resent_from_field'
|
188
221
|
def underscoreize( str )
|
189
|
-
str.to_s.downcase.
|
222
|
+
str.to_s.downcase.tr(Constants::HYPHEN, Constants::UNDERSCORE)
|
223
|
+
end
|
224
|
+
|
225
|
+
def map_lines( str, &block )
|
226
|
+
str.each_line.map(&block)
|
227
|
+
end
|
228
|
+
|
229
|
+
def map_with_index( enum, &block )
|
230
|
+
enum.each_with_index.map(&block)
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.binary_unsafe_to_lf(string) #:nodoc:
|
234
|
+
string.gsub(/\r\n|\r/, Constants::LF)
|
235
|
+
end
|
236
|
+
|
237
|
+
TO_CRLF_REGEX =
|
238
|
+
# This 1.9 only regex can save a reasonable amount of time (~20%)
|
239
|
+
# by not matching "\r\n" so the string is returned unchanged in
|
240
|
+
# the common case.
|
241
|
+
Regexp.new("(?<!\r)\n|\r(?!\n)")
|
242
|
+
|
243
|
+
def self.binary_unsafe_to_crlf(string) #:nodoc:
|
244
|
+
string.gsub(TO_CRLF_REGEX, Constants::CRLF)
|
245
|
+
end
|
246
|
+
|
247
|
+
def self.safe_for_line_ending_conversion?(string) #:nodoc:
|
248
|
+
if string.encoding == Encoding::BINARY
|
249
|
+
string.ascii_only?
|
250
|
+
else
|
251
|
+
string.valid_encoding?
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Convert line endings to \n unless the string is binary. Used for
|
256
|
+
# sendmail delivery and for decoding 8bit Content-Transfer-Encoding.
|
257
|
+
def self.to_lf(string)
|
258
|
+
string = string.to_s
|
259
|
+
if safe_for_line_ending_conversion? string
|
260
|
+
binary_unsafe_to_lf string
|
261
|
+
else
|
262
|
+
string
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Convert line endings to \r\n unless the string is binary. Used for
|
267
|
+
# encoding 8bit and base64 Content-Transfer-Encoding and for convenience
|
268
|
+
# when parsing emails with \n line endings instead of the required \r\n.
|
269
|
+
def self.to_crlf(string)
|
270
|
+
string = string.to_s
|
271
|
+
if safe_for_line_ending_conversion? string
|
272
|
+
binary_unsafe_to_crlf string
|
273
|
+
else
|
274
|
+
string
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Returns true if the object is considered blank.
|
279
|
+
# A blank includes things like '', ' ', nil,
|
280
|
+
# and arrays and hashes that have nothing in them.
|
281
|
+
#
|
282
|
+
# This logic is mostly shared with ActiveSupport's blank?
|
283
|
+
def blank?(value)
|
284
|
+
if value.kind_of?(NilClass)
|
285
|
+
true
|
286
|
+
elsif value.kind_of?(String)
|
287
|
+
value !~ /\S/
|
288
|
+
else
|
289
|
+
value.respond_to?(:empty?) ? value.empty? : !value
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def generate_message_id
|
294
|
+
"<#{Mail.random_tag}@#{::Socket.gethostname}.mail>"
|
190
295
|
end
|
191
296
|
|
192
|
-
|
297
|
+
class StrictCharsetEncoder
|
298
|
+
def encode(string, charset)
|
299
|
+
case charset
|
300
|
+
when /utf-?7/i
|
301
|
+
Mail::Utilities.decode_utf7(string)
|
302
|
+
else
|
303
|
+
string.force_encoding(Mail::Utilities.pick_encoding(charset))
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
193
307
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
308
|
+
class BestEffortCharsetEncoder
|
309
|
+
def encode(string, charset)
|
310
|
+
case charset
|
311
|
+
when /utf-?7/i
|
312
|
+
Mail::Utilities.decode_utf7(string)
|
313
|
+
else
|
314
|
+
string.force_encoding(pick_encoding(charset))
|
198
315
|
end
|
199
|
-
results
|
200
316
|
end
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
317
|
+
|
318
|
+
private
|
319
|
+
|
320
|
+
def pick_encoding(charset)
|
321
|
+
charset = case charset
|
322
|
+
when /ansi_x3.110-1983/
|
323
|
+
'ISO-8859-1'
|
324
|
+
when /Windows-?1258/i # Windows-1258 is similar to 1252
|
325
|
+
"Windows-1252"
|
326
|
+
else
|
327
|
+
charset
|
206
328
|
end
|
207
|
-
|
329
|
+
Mail::Utilities.pick_encoding(charset)
|
208
330
|
end
|
209
|
-
|
210
|
-
|
331
|
+
end
|
332
|
+
|
333
|
+
class << self
|
334
|
+
attr_accessor :charset_encoder
|
335
|
+
end
|
336
|
+
self.charset_encoder = BestEffortCharsetEncoder.new
|
337
|
+
|
338
|
+
# Escapes any parenthesis in a string that are unescaped this uses
|
339
|
+
# a Ruby 1.9.1 regexp feature of negative look behind
|
340
|
+
def Utilities.escape_paren( str )
|
341
|
+
re = /(?<!\\)([\(\)])/ # Only match unescaped parens
|
342
|
+
str.gsub(re) { |s| '\\' + s }
|
343
|
+
end
|
344
|
+
|
345
|
+
def Utilities.paren( str )
|
346
|
+
str = ::Mail::Utilities.unparen( str )
|
347
|
+
str = escape_paren( str )
|
348
|
+
'(' + str + ')'
|
349
|
+
end
|
350
|
+
|
351
|
+
def Utilities.escape_bracket( str )
|
352
|
+
re = /(?<!\\)([\<\>])/ # Only match unescaped brackets
|
353
|
+
str.gsub(re) { |s| '\\' + s }
|
354
|
+
end
|
355
|
+
|
356
|
+
def Utilities.bracket( str )
|
357
|
+
str = ::Mail::Utilities.unbracket( str )
|
358
|
+
str = escape_bracket( str )
|
359
|
+
'<' + str + '>'
|
360
|
+
end
|
211
361
|
|
212
|
-
|
213
|
-
|
362
|
+
def Utilities.decode_base64(str)
|
363
|
+
if !str.end_with?("=") && str.length % 4 != 0
|
364
|
+
str = str.ljust((str.length + 3) & ~3, "=")
|
214
365
|
end
|
366
|
+
str.unpack( 'm' ).first
|
367
|
+
end
|
368
|
+
|
369
|
+
def Utilities.encode_base64(str)
|
370
|
+
[str].pack( 'm' )
|
371
|
+
end
|
372
|
+
|
373
|
+
def Utilities.has_constant?(klass, string)
|
374
|
+
klass.const_defined?( string, false )
|
375
|
+
end
|
376
|
+
|
377
|
+
def Utilities.get_constant(klass, string)
|
378
|
+
klass.const_get( string )
|
379
|
+
end
|
380
|
+
|
381
|
+
def Utilities.transcode_charset(str, from_encoding, to_encoding = Encoding::UTF_8)
|
382
|
+
to_encoding = Encoding.find(to_encoding)
|
383
|
+
replacement_char = to_encoding == Encoding::UTF_8 ? '�' : '?'
|
384
|
+
charset_encoder.encode(str.dup, from_encoding).encode(to_encoding, :undef => :replace, :invalid => :replace, :replace => replacement_char)
|
385
|
+
end
|
386
|
+
|
387
|
+
# From Ruby stdlib Net::IMAP
|
388
|
+
def Utilities.encode_utf7(string)
|
389
|
+
string.gsub(/(&)|[^\x20-\x7e]+/) do
|
390
|
+
if $1
|
391
|
+
"&-"
|
392
|
+
else
|
393
|
+
base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0")
|
394
|
+
"&" + base64.delete("=").tr("/", ",") + "-"
|
395
|
+
end
|
396
|
+
end.force_encoding(Encoding::ASCII_8BIT)
|
397
|
+
end
|
215
398
|
|
216
|
-
|
217
|
-
|
399
|
+
def Utilities.decode_utf7(utf7)
|
400
|
+
utf7.gsub(/&([^-]+)?-/n) do
|
401
|
+
if $1
|
402
|
+
($1.tr(",", "/") + "===").unpack("m")[0].encode(Encoding::UTF_8, Encoding::UTF_16BE)
|
403
|
+
else
|
404
|
+
"&"
|
405
|
+
end
|
218
406
|
end
|
407
|
+
end
|
219
408
|
|
409
|
+
def Utilities.b_value_encode(str, encoding = nil)
|
410
|
+
encoding = str.encoding.to_s
|
411
|
+
[Utilities.encode_base64(str), encoding]
|
220
412
|
end
|
221
413
|
|
414
|
+
def Utilities.b_value_decode(str)
|
415
|
+
match = str.match(/\=\?(.+)?\?[Bb]\?(.*)\?\=/m)
|
416
|
+
if match
|
417
|
+
charset = match[1]
|
418
|
+
str = Utilities.decode_base64(match[2])
|
419
|
+
str = charset_encoder.encode(str, charset)
|
420
|
+
end
|
421
|
+
transcode_to_scrubbed_utf8(str)
|
422
|
+
rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError, Encoding::InvalidByteSequenceError
|
423
|
+
warn "Encoding conversion failed #{$!}"
|
424
|
+
str.dup.force_encoding(Encoding::UTF_8)
|
425
|
+
end
|
426
|
+
|
427
|
+
def Utilities.q_value_encode(str, encoding = nil)
|
428
|
+
encoding = str.encoding.to_s
|
429
|
+
[Encodings::QuotedPrintable.encode(str), encoding]
|
430
|
+
end
|
431
|
+
|
432
|
+
def Utilities.q_value_decode(str)
|
433
|
+
match = str.match(/\=\?(.+)?\?[Qq]\?(.*)\?\=/m)
|
434
|
+
if match
|
435
|
+
charset = match[1]
|
436
|
+
string = match[2].gsub(/_/, '=20')
|
437
|
+
# Remove trailing = if it exists in a Q encoding
|
438
|
+
string = string.sub(/\=$/, '')
|
439
|
+
str = Encodings::QuotedPrintable.decode(string)
|
440
|
+
str = charset_encoder.encode(str, charset)
|
441
|
+
# We assume that binary strings hold utf-8 directly to work around
|
442
|
+
# jruby/jruby#829 which subtly changes String#encode semantics.
|
443
|
+
str.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::ASCII_8BIT
|
444
|
+
end
|
445
|
+
transcode_to_scrubbed_utf8(str)
|
446
|
+
rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
|
447
|
+
warn "Encoding conversion failed #{$!}"
|
448
|
+
str.dup.force_encoding(Encoding::UTF_8)
|
449
|
+
end
|
450
|
+
|
451
|
+
def Utilities.param_decode(str, encoding)
|
452
|
+
str = uri_parser.unescape(str)
|
453
|
+
str = charset_encoder.encode(str, encoding) if encoding
|
454
|
+
transcode_to_scrubbed_utf8(str)
|
455
|
+
rescue Encoding::UndefinedConversionError, ArgumentError, Encoding::ConverterNotFoundError
|
456
|
+
warn "Encoding conversion failed #{$!}"
|
457
|
+
str.dup.force_encoding(Encoding::UTF_8)
|
458
|
+
end
|
459
|
+
|
460
|
+
def Utilities.param_encode(str)
|
461
|
+
encoding = str.encoding.to_s.downcase
|
462
|
+
language = Configuration.instance.param_encode_language
|
463
|
+
"#{encoding}'#{language}'#{uri_parser.escape(str)}"
|
464
|
+
end
|
465
|
+
|
466
|
+
def Utilities.uri_parser
|
467
|
+
URI::DEFAULT_PARSER
|
468
|
+
end
|
469
|
+
|
470
|
+
# Pick a Ruby encoding corresponding to the message charset. Most
|
471
|
+
# charsets have a Ruby encoding, but some need manual aliasing here.
|
472
|
+
#
|
473
|
+
# TODO: add this as a test somewhere:
|
474
|
+
# Encoding.list.map { |e| [e.to_s.upcase == pick_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
|
475
|
+
# Encoding.list.map { |e| [e.to_s == pick_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
|
476
|
+
def Utilities.pick_encoding(charset)
|
477
|
+
charset = charset.to_s
|
478
|
+
encoding = case charset.downcase
|
479
|
+
|
480
|
+
# ISO-8859-8-I etc. http://en.wikipedia.org/wiki/ISO-8859-8-I
|
481
|
+
when /^iso[-_]?8859-(\d+)(-i)?$/
|
482
|
+
"ISO-8859-#{$1}"
|
483
|
+
|
484
|
+
# ISO-8859-15, ISO-2022-JP and alike
|
485
|
+
when /^iso[-_]?(\d{4})-?(\w{1,2})$/
|
486
|
+
"ISO-#{$1}-#{$2}"
|
487
|
+
|
488
|
+
# "ISO-2022-JP-KDDI" and alike
|
489
|
+
when /^iso[-_]?(\d{4})-?(\w{1,2})-?(\w*)$/
|
490
|
+
"ISO-#{$1}-#{$2}-#{$3}"
|
491
|
+
|
492
|
+
# UTF-8, UTF-32BE and alike
|
493
|
+
when /^utf[\-_]?(\d{1,2})?(\w{1,2})$/
|
494
|
+
"UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
|
495
|
+
|
496
|
+
# Windows-1252 and alike
|
497
|
+
when /^windows-?(.*)$/
|
498
|
+
"Windows-#{$1}"
|
499
|
+
|
500
|
+
when '8bit'
|
501
|
+
Encoding::ASCII_8BIT
|
502
|
+
|
503
|
+
# alternatives/misspellings of us-ascii seen in the wild
|
504
|
+
when /^iso[-_]?646(-us)?$/, 'us=ascii'
|
505
|
+
Encoding::ASCII
|
506
|
+
|
507
|
+
# Microsoft-specific alias for MACROMAN
|
508
|
+
when 'macintosh'
|
509
|
+
Encoding::MACROMAN
|
510
|
+
|
511
|
+
# Microsoft-specific alias for CP949 (Korean)
|
512
|
+
when 'ks_c_5601-1987'
|
513
|
+
Encoding::CP949
|
514
|
+
|
515
|
+
# Wrongly written Shift_JIS (Japanese)
|
516
|
+
when 'shift-jis'
|
517
|
+
Encoding::Shift_JIS
|
518
|
+
|
519
|
+
# GB2312 (Chinese charset) is a subset of GB18030 (its replacement)
|
520
|
+
when 'gb2312'
|
521
|
+
Encoding::GB18030
|
522
|
+
|
523
|
+
when 'cp-850'
|
524
|
+
Encoding::CP850
|
525
|
+
|
526
|
+
when 'latin2'
|
527
|
+
Encoding::ISO_8859_2
|
528
|
+
|
529
|
+
else
|
530
|
+
charset
|
531
|
+
end
|
532
|
+
|
533
|
+
convert_to_encoding(encoding)
|
534
|
+
end
|
535
|
+
|
536
|
+
def Utilities.string_byteslice(str, *args)
|
537
|
+
str.byteslice(*args)
|
538
|
+
end
|
539
|
+
|
540
|
+
class << self
|
541
|
+
private
|
542
|
+
|
543
|
+
def convert_to_encoding(encoding)
|
544
|
+
if encoding.is_a?(Encoding)
|
545
|
+
encoding
|
546
|
+
else
|
547
|
+
# Fall back to ASCII for charsets that Ruby doesn't recognize
|
548
|
+
begin
|
549
|
+
Encoding.find(encoding)
|
550
|
+
rescue ArgumentError
|
551
|
+
Encoding::BINARY
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
def transcode_to_scrubbed_utf8(str)
|
557
|
+
decoded = str.encode(Encoding::UTF_8, :undef => :replace, :invalid => :replace, :replace => "�")
|
558
|
+
decoded.valid_encoding? ? decoded : decoded.encode(Encoding::UTF_16LE, :invalid => :replace, :replace => "�").encode(Encoding::UTF_8)
|
559
|
+
end
|
560
|
+
end
|
222
561
|
end
|
223
562
|
end
|
Binary file
|