mail 2.4.4 → 2.5.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of mail might be problematic. Click here for more details.
- data/CHANGELOG.rdoc +60 -0
- data/Gemfile +1 -1
- data/README.md +11 -10
- data/lib/VERSION +2 -2
- data/lib/load_parsers.rb +41 -0
- data/lib/mail.rb +18 -17
- data/lib/mail/body.rb +2 -2
- data/lib/mail/check_delivery_params.rb +30 -0
- data/lib/mail/configuration.rb +1 -1
- data/lib/mail/core_extensions/nil.rb +4 -2
- data/lib/mail/core_extensions/shell_escape.rb +1 -1
- data/lib/mail/elements.rb +12 -12
- data/lib/mail/encodings.rb +8 -10
- data/lib/mail/encodings/quoted_printable.rb +1 -1
- data/lib/mail/field.rb +73 -95
- data/lib/mail/fields.rb +32 -32
- data/lib/mail/fields/common/common_address.rb +6 -1
- data/lib/mail/fields/common/common_field.rb +7 -1
- data/lib/mail/fields/common/common_message_id.rb +9 -5
- data/lib/mail/fields/content_disposition_field.rb +1 -0
- data/lib/mail/fields/content_type_field.rb +4 -1
- data/lib/mail/fields/keywords_field.rb +1 -1
- data/lib/mail/fields/unstructured_field.rb +33 -26
- data/lib/mail/header.rb +32 -13
- data/lib/mail/message.rb +8 -9
- data/lib/mail/multibyte/chars.rb +2 -2
- data/lib/mail/multibyte/unicode.rb +6 -0
- data/lib/mail/network.rb +9 -9
- data/lib/mail/network/delivery_methods/exim.rb +5 -1
- data/lib/mail/network/delivery_methods/file_delivery.rb +5 -0
- data/lib/mail/network/delivery_methods/sendmail.rb +5 -0
- data/lib/mail/network/delivery_methods/smtp.rb +11 -19
- data/lib/mail/network/delivery_methods/smtp_connection.rb +5 -18
- data/lib/mail/network/delivery_methods/test_mailer.rb +5 -1
- data/lib/mail/network/retriever_methods/imap.rb +14 -6
- data/lib/mail/network/retriever_methods/pop3.rb +2 -2
- data/lib/mail/network/retriever_methods/test_retriever.rb +11 -15
- data/lib/mail/parsers/rfc2822.rb +77 -21
- data/lib/mail/parsers/rfc2822.treetop +3 -3
- data/lib/mail/patterns.rb +0 -1
- data/lib/mail/values/unicode_tables.dat +0 -0
- data/lib/mail/version_specific/ruby_1_8.rb +18 -1
- data/lib/mail/version_specific/ruby_1_9.rb +7 -1
- metadata +27 -10
- data/Gemfile.lock +0 -36
data/lib/mail/fields.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
1
|
module Mail
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
register_autoload :UnstructuredField, 'mail/fields/unstructured_field'
|
3
|
+
register_autoload :StructuredField, 'mail/fields/structured_field'
|
4
|
+
register_autoload :OptionalField, 'mail/fields/optional_field'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
6
|
+
register_autoload :BccField, 'mail/fields/bcc_field'
|
7
|
+
register_autoload :CcField, 'mail/fields/cc_field'
|
8
|
+
register_autoload :CommentsField, 'mail/fields/comments_field'
|
9
|
+
register_autoload :ContentDescriptionField, 'mail/fields/content_description_field'
|
10
|
+
register_autoload :ContentDispositionField, 'mail/fields/content_disposition_field'
|
11
|
+
register_autoload :ContentIdField, 'mail/fields/content_id_field'
|
12
|
+
register_autoload :ContentLocationField, 'mail/fields/content_location_field'
|
13
|
+
register_autoload :ContentTransferEncodingField, 'mail/fields/content_transfer_encoding_field'
|
14
|
+
register_autoload :ContentTypeField, 'mail/fields/content_type_field'
|
15
|
+
register_autoload :DateField, 'mail/fields/date_field'
|
16
|
+
register_autoload :FromField, 'mail/fields/from_field'
|
17
|
+
register_autoload :InReplyToField, 'mail/fields/in_reply_to_field'
|
18
|
+
register_autoload :KeywordsField, 'mail/fields/keywords_field'
|
19
|
+
register_autoload :MessageIdField, 'mail/fields/message_id_field'
|
20
|
+
register_autoload :MimeVersionField, 'mail/fields/mime_version_field'
|
21
|
+
register_autoload :ReceivedField, 'mail/fields/received_field'
|
22
|
+
register_autoload :ReferencesField, 'mail/fields/references_field'
|
23
|
+
register_autoload :ReplyToField, 'mail/fields/reply_to_field'
|
24
|
+
register_autoload :ResentBccField, 'mail/fields/resent_bcc_field'
|
25
|
+
register_autoload :ResentCcField, 'mail/fields/resent_cc_field'
|
26
|
+
register_autoload :ResentDateField, 'mail/fields/resent_date_field'
|
27
|
+
register_autoload :ResentFromField, 'mail/fields/resent_from_field'
|
28
|
+
register_autoload :ResentMessageIdField, 'mail/fields/resent_message_id_field'
|
29
|
+
register_autoload :ResentSenderField, 'mail/fields/resent_sender_field'
|
30
|
+
register_autoload :ResentToField, 'mail/fields/resent_to_field'
|
31
|
+
register_autoload :ReturnPathField, 'mail/fields/return_path_field'
|
32
|
+
register_autoload :SenderField, 'mail/fields/sender_field'
|
33
|
+
register_autoload :SubjectField, 'mail/fields/subject_field'
|
34
|
+
register_autoload :ToField, 'mail/fields/to_field'
|
35
35
|
end
|
@@ -81,9 +81,14 @@ module Mail
|
|
81
81
|
when val.blank?
|
82
82
|
parse(encoded)
|
83
83
|
else
|
84
|
-
|
84
|
+
self.value = [self.value, val].reject {|a| a.blank? }.join(", ")
|
85
85
|
end
|
86
86
|
end
|
87
|
+
|
88
|
+
def value=(val)
|
89
|
+
super
|
90
|
+
parse(self.value)
|
91
|
+
end
|
87
92
|
|
88
93
|
private
|
89
94
|
|
@@ -34,7 +34,7 @@ module Mail
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def responsible_for?( val )
|
37
|
-
name.to_s.
|
37
|
+
name.to_s.casecmp(val.to_s) == 0
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
@@ -47,5 +47,11 @@ module Mail
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
+
def ensure_filename_quoted(value)
|
51
|
+
if !value.is_a?(Array) and /(.)*\s(filename|name)=[^"](.+\s)+[^"]/.match value
|
52
|
+
value.gsub!(/[^=]+$/, '"\\0"')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
50
56
|
end
|
51
57
|
end
|
@@ -31,14 +31,18 @@ module Mail
|
|
31
31
|
end
|
32
32
|
|
33
33
|
private
|
34
|
-
|
34
|
+
|
35
35
|
def do_encode(field_name)
|
36
|
-
%Q{#{field_name}: #{
|
36
|
+
%Q{#{field_name}: #{formated_message_ids("\r\n ")}\r\n}
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
def do_decode
|
40
|
-
|
40
|
+
formated_message_ids(' ')
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
|
+
def formated_message_ids(join)
|
44
|
+
message_ids.map{ |m| "<#{m}>" }.join(join) if message_ids
|
45
|
+
end
|
46
|
+
|
43
47
|
end
|
44
48
|
end
|
@@ -19,6 +19,7 @@ module Mail
|
|
19
19
|
@parameters = nil
|
20
20
|
value = strip_field(FIELD_NAME, value)
|
21
21
|
end
|
22
|
+
ensure_filename_quoted(value)
|
22
23
|
super(CAPITALIZED_FIELD, value, charset)
|
23
24
|
self.parse
|
24
25
|
self
|
@@ -141,7 +142,9 @@ module Mail
|
|
141
142
|
def sanatize( val )
|
142
143
|
|
143
144
|
# TODO: check if there are cases where whitespace is not a separator
|
144
|
-
val = val.
|
145
|
+
val = val.
|
146
|
+
gsub(/\s*=\s*/,'='). # remove whitespaces around equal sign
|
147
|
+
tr(' ',';').
|
145
148
|
squeeze(';').
|
146
149
|
gsub(';', '; '). #use '; ' as a separator (or EOL)
|
147
150
|
gsub(/;\s*$/,'') #remove trailing to keep examples below
|
@@ -6,7 +6,7 @@ module Mail
|
|
6
6
|
#
|
7
7
|
# ===Per RFC 2822:
|
8
8
|
# 2.2.1. Unstructured Header Field Bodies
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# Some field bodies in this standard are defined simply as
|
11
11
|
# "unstructured" (which is specified below as any US-ASCII characters,
|
12
12
|
# except for CR and LF) with no further restrictions. These are
|
@@ -15,20 +15,29 @@ module Mail
|
|
15
15
|
# with no further processing (except for header "folding" and
|
16
16
|
# "unfolding" as described in section 2.2.3).
|
17
17
|
class UnstructuredField
|
18
|
-
|
18
|
+
|
19
19
|
include Mail::CommonField
|
20
20
|
include Mail::Utilities
|
21
|
-
|
21
|
+
|
22
22
|
attr_accessor :charset
|
23
23
|
attr_reader :errors
|
24
|
-
|
24
|
+
|
25
25
|
def initialize(name, value, charset = nil)
|
26
26
|
@errors = []
|
27
|
+
|
28
|
+
if value.is_a?(Array)
|
29
|
+
# Probably has arrived here from a failed parse of an AddressList Field
|
30
|
+
value = value.join(', ')
|
31
|
+
else
|
32
|
+
# Ensure we are dealing with a string
|
33
|
+
value = value.to_s
|
34
|
+
end
|
35
|
+
|
27
36
|
if charset
|
28
37
|
self.charset = charset
|
29
38
|
else
|
30
|
-
if value.
|
31
|
-
self.charset = value.
|
39
|
+
if value.respond_to?(:encoding)
|
40
|
+
self.charset = value.encoding
|
32
41
|
else
|
33
42
|
self.charset = $KCODE
|
34
43
|
end
|
@@ -37,11 +46,11 @@ module Mail
|
|
37
46
|
self.value = value
|
38
47
|
self
|
39
48
|
end
|
40
|
-
|
49
|
+
|
41
50
|
def encoded
|
42
51
|
do_encode
|
43
52
|
end
|
44
|
-
|
53
|
+
|
45
54
|
def decoded
|
46
55
|
do_decode
|
47
56
|
end
|
@@ -49,25 +58,23 @@ module Mail
|
|
49
58
|
def default
|
50
59
|
decoded
|
51
60
|
end
|
52
|
-
|
61
|
+
|
53
62
|
def parse # An unstructured field does not parse
|
54
63
|
self
|
55
64
|
end
|
56
65
|
|
57
66
|
private
|
58
|
-
|
67
|
+
|
59
68
|
def do_encode
|
60
69
|
value.nil? ? '' : "#{wrapped_value}\r\n"
|
61
70
|
end
|
62
|
-
|
71
|
+
|
63
72
|
def do_decode
|
64
|
-
|
65
|
-
result.encode!(value.encoding || "UTF-8") if RUBY_VERSION >= '1.9' && !result.blank?
|
66
|
-
result
|
73
|
+
value.blank? ? nil : Encodings.decode_encode(value, :decode)
|
67
74
|
end
|
68
|
-
|
75
|
+
|
69
76
|
# 2.2.3. Long Header Fields
|
70
|
-
#
|
77
|
+
#
|
71
78
|
# Each header field is logically a single line of characters comprising
|
72
79
|
# the field name, the colon, and the field body. For convenience
|
73
80
|
# however, and to deal with the 998/78 character limitations per line,
|
@@ -76,14 +83,14 @@ module Mail
|
|
76
83
|
# that wherever this standard allows for folding white space (not
|
77
84
|
# simply WSP characters), a CRLF may be inserted before any WSP. For
|
78
85
|
# example, the header field:
|
79
|
-
#
|
86
|
+
#
|
80
87
|
# Subject: This is a test
|
81
|
-
#
|
88
|
+
#
|
82
89
|
# can be represented as:
|
83
|
-
#
|
90
|
+
#
|
84
91
|
# Subject: This
|
85
92
|
# is a test
|
86
|
-
#
|
93
|
+
#
|
87
94
|
# Note: Though structured field bodies are defined in such a way that
|
88
95
|
# folding can take place between many of the lexical tokens (and even
|
89
96
|
# within some of the lexical tokens), folding SHOULD be limited to
|
@@ -95,9 +102,9 @@ module Mail
|
|
95
102
|
def wrapped_value # :nodoc:
|
96
103
|
wrap_lines(name, fold("#{name}: ".length))
|
97
104
|
end
|
98
|
-
|
105
|
+
|
99
106
|
# 6.2. Display of 'encoded-word's
|
100
|
-
#
|
107
|
+
#
|
101
108
|
# When displaying a particular header field that contains multiple
|
102
109
|
# 'encoded-word's, any 'linear-white-space' that separates a pair of
|
103
110
|
# adjacent 'encoded-word's is ignored. (This is to allow the use of
|
@@ -131,7 +138,7 @@ module Mail
|
|
131
138
|
else
|
132
139
|
words = decoded_string.split(/[ \t]/)
|
133
140
|
end
|
134
|
-
|
141
|
+
|
135
142
|
folded_lines = []
|
136
143
|
while !words.empty?
|
137
144
|
limit = 78 - prepend
|
@@ -145,7 +152,7 @@ module Mail
|
|
145
152
|
# Skip to next line if we're going to go past the limit
|
146
153
|
# Unless this is the first word, in which case we're going to add it anyway
|
147
154
|
# Note: This means that a word that's longer than 998 characters is going to break the spec. Please fix if this is a problem for you.
|
148
|
-
# (The fix, it seems, would be to use encoded-word encoding on it, because that way you can break it across multiple lines and
|
155
|
+
# (The fix, it seems, would be to use encoded-word encoding on it, because that way you can break it across multiple lines and
|
149
156
|
# the linebreak will be ignored)
|
150
157
|
break if !line.empty? && (line.length + word.length + 1 > limit)
|
151
158
|
# Remove the word from the queue ...
|
@@ -153,7 +160,7 @@ module Mail
|
|
153
160
|
# Add word separator
|
154
161
|
line << " " unless (line.empty? || should_encode)
|
155
162
|
# ... add it in encoded form to the current line
|
156
|
-
line << word
|
163
|
+
line << word
|
157
164
|
end
|
158
165
|
# Encode the line if necessary
|
159
166
|
line = "=?#{encoding}?Q?#{line}?=" if should_encode
|
@@ -163,7 +170,7 @@ module Mail
|
|
163
170
|
end
|
164
171
|
folded_lines
|
165
172
|
end
|
166
|
-
|
173
|
+
|
167
174
|
def encode(value)
|
168
175
|
value = [value].pack("M").gsub("=\n", '')
|
169
176
|
value.gsub!(/"/, '=22')
|
data/lib/mail/header.rb
CHANGED
@@ -21,6 +21,20 @@ module Mail
|
|
21
21
|
include Utilities
|
22
22
|
include Enumerable
|
23
23
|
|
24
|
+
@@maximum_amount = 1000
|
25
|
+
|
26
|
+
# Large amount of headers in Email might create extra high CPU load
|
27
|
+
# Use this parameter to limit number of headers that will be parsed by
|
28
|
+
# mail library.
|
29
|
+
# Default: 1000
|
30
|
+
def self.maximum_amount
|
31
|
+
@@maximum_amount
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.maximum_amount=(value)
|
35
|
+
@@maximum_amount = value
|
36
|
+
end
|
37
|
+
|
24
38
|
# Creates a new header object.
|
25
39
|
#
|
26
40
|
# Accepts raw text or nothing. If given raw text will attempt to parse
|
@@ -73,14 +87,12 @@ module Mail
|
|
73
87
|
# h.fields = ['From: mikel@me.com', 'To: bob@you.com']
|
74
88
|
def fields=(unfolded_fields)
|
75
89
|
@fields = Mail::FieldList.new
|
76
|
-
warn "Warning: more than
|
77
|
-
unfolded_fields[0..
|
90
|
+
warn "Warning: more than #{self.class.maximum_amount} header fields only using the first #{self.class.maximum_amount}" if unfolded_fields.length > self.class.maximum_amount
|
91
|
+
unfolded_fields[0..(self.class.maximum_amount-1)].each do |field|
|
78
92
|
|
79
93
|
field = Field.new(field, nil, charset)
|
80
94
|
field.errors.each { |error| self.errors << error }
|
81
|
-
selected = select_field_for(field.name)
|
82
|
-
|
83
|
-
if selected.any? && limited_field?(field.name)
|
95
|
+
if limited_field?(field.name) && (selected = select_field_for(field.name)) && selected.any?
|
84
96
|
selected.first.update(field.name, field.value)
|
85
97
|
else
|
86
98
|
@fields << field
|
@@ -159,15 +171,15 @@ module Mail
|
|
159
171
|
# Need to insert in correct order for trace fields
|
160
172
|
self.fields << Field.new(name.to_s, value, charset)
|
161
173
|
end
|
174
|
+
if dasherize(fn) == "content-type"
|
175
|
+
# Update charset if specified in Content-Type
|
176
|
+
params = self[:content_type].parameters rescue nil
|
177
|
+
@charset = params && params[:charset]
|
178
|
+
end
|
162
179
|
end
|
163
180
|
|
164
181
|
def charset
|
165
|
-
|
166
|
-
if params
|
167
|
-
params[:charset]
|
168
|
-
else
|
169
|
-
@charset
|
170
|
-
end
|
182
|
+
@charset
|
171
183
|
end
|
172
184
|
|
173
185
|
def charset=(val)
|
@@ -254,12 +266,19 @@ module Mail
|
|
254
266
|
end
|
255
267
|
|
256
268
|
def select_field_for(name)
|
257
|
-
fields.select { |f| f.responsible_for?(name
|
269
|
+
fields.select { |f| f.responsible_for?(name) }
|
258
270
|
end
|
259
271
|
|
260
272
|
def limited_field?(name)
|
261
273
|
LIMITED_FIELDS.include?(name.to_s.downcase)
|
262
274
|
end
|
263
|
-
|
275
|
+
|
276
|
+
# Enumerable support; yield each field in order to the block if there is one,
|
277
|
+
# or return an Enumerator for them if there isn't.
|
278
|
+
def each( &block )
|
279
|
+
return self.fields.each( &block ) if block
|
280
|
+
self.fields.each
|
281
|
+
end
|
282
|
+
|
264
283
|
end
|
265
284
|
end
|
data/lib/mail/message.rb
CHANGED
@@ -1411,7 +1411,7 @@ module Mail
|
|
1411
1411
|
|
1412
1412
|
# Returns the MIME media type of part we are on, this is taken from the content-type header
|
1413
1413
|
def mime_type
|
1414
|
-
|
1414
|
+
has_content_type? ? header[:content_type].string : nil rescue nil
|
1415
1415
|
end
|
1416
1416
|
|
1417
1417
|
def message_content_type
|
@@ -1422,7 +1422,7 @@ module Mail
|
|
1422
1422
|
# Returns the character set defined in the content type field
|
1423
1423
|
def charset
|
1424
1424
|
if @header
|
1425
|
-
|
1425
|
+
has_content_type? ? content_type_parameters['charset'] : @charset
|
1426
1426
|
else
|
1427
1427
|
@charset
|
1428
1428
|
end
|
@@ -1875,15 +1875,13 @@ module Mail
|
|
1875
1875
|
# Additionally, I allow for the case where someone might have put whitespace
|
1876
1876
|
# on the "gap line"
|
1877
1877
|
def parse_message
|
1878
|
-
header_part, body_part = raw_source.split(/#{CRLF}#{WSP}*#{CRLF}/m, 2)
|
1879
|
-
# index = raw_source.index(/#{CRLF}#{WSP}*#{CRLF}/m, 2)
|
1880
|
-
# self.header = (index) ? header_part[0,index] : nil
|
1881
|
-
# lazy_body ( [raw_source, index+1])
|
1878
|
+
header_part, body_part = raw_source.split(/#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m, 2)
|
1882
1879
|
self.header = header_part
|
1883
1880
|
self.body = body_part
|
1884
1881
|
end
|
1885
1882
|
|
1886
1883
|
def raw_source=(value)
|
1884
|
+
value.force_encoding("binary") if RUBY_VERSION >= "1.9.1"
|
1887
1885
|
@raw_source = value.to_crlf
|
1888
1886
|
end
|
1889
1887
|
|
@@ -1914,9 +1912,10 @@ module Mail
|
|
1914
1912
|
end
|
1915
1913
|
|
1916
1914
|
def set_envelope_header
|
1917
|
-
|
1915
|
+
raw_string = raw_source.to_s
|
1916
|
+
if match_data = raw_source.to_s.match(/\AFrom\s(#{TEXT}+)#{CRLF}/m)
|
1918
1917
|
set_envelope(match_data[1])
|
1919
|
-
self.raw_source = match_data[
|
1918
|
+
self.raw_source = raw_string.sub(match_data[0], "")
|
1920
1919
|
end
|
1921
1920
|
end
|
1922
1921
|
|
@@ -2047,7 +2046,7 @@ module Mail
|
|
2047
2046
|
else
|
2048
2047
|
if encoding = Encoding.find(charset) rescue nil
|
2049
2048
|
body_text.force_encoding(encoding)
|
2050
|
-
return body_text.encode(Encoding::UTF_8)
|
2049
|
+
return body_text.encode(Encoding::UTF_8, :undef => :replace, :invalid => :replace, :replace => '')
|
2051
2050
|
end
|
2052
2051
|
end
|
2053
2052
|
end
|