mail 2.1.5.3 → 2.2.0

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.

Files changed (60) hide show
  1. data/CHANGELOG.rdoc +16 -0
  2. data/Rakefile +1 -1
  3. data/lib/mail.rb +3 -1
  4. data/lib/mail/body.rb +3 -2
  5. data/lib/mail/core_extensions/string.rb +4 -0
  6. data/lib/mail/elements/address.rb +3 -3
  7. data/lib/mail/elements/content_transfer_encoding_element.rb +1 -1
  8. data/lib/mail/encodings.rb +48 -8
  9. data/lib/mail/encodings/quoted_printable.rb +1 -14
  10. data/lib/mail/field.rb +44 -44
  11. data/lib/mail/fields/bcc_field.rb +3 -2
  12. data/lib/mail/fields/cc_field.rb +3 -2
  13. data/lib/mail/fields/comments_field.rb +3 -3
  14. data/lib/mail/fields/common/common_address.rb +19 -10
  15. data/lib/mail/fields/common/common_field.rb +6 -6
  16. data/lib/mail/fields/common/common_message_id.rb +1 -1
  17. data/lib/mail/fields/content_description_field.rb +3 -3
  18. data/lib/mail/fields/content_disposition_field.rb +3 -3
  19. data/lib/mail/fields/content_id_field.rb +6 -6
  20. data/lib/mail/fields/content_location_field.rb +3 -3
  21. data/lib/mail/fields/content_transfer_encoding_field.rb +6 -3
  22. data/lib/mail/fields/content_type_field.rb +8 -7
  23. data/lib/mail/fields/date_field.rb +8 -8
  24. data/lib/mail/fields/from_field.rb +3 -3
  25. data/lib/mail/fields/in_reply_to_field.rb +3 -2
  26. data/lib/mail/fields/keywords_field.rb +3 -2
  27. data/lib/mail/fields/message_id_field.rb +4 -3
  28. data/lib/mail/fields/mime_version_field.rb +5 -6
  29. data/lib/mail/fields/received_field.rb +3 -2
  30. data/lib/mail/fields/references_field.rb +3 -3
  31. data/lib/mail/fields/reply_to_field.rb +3 -3
  32. data/lib/mail/fields/resent_bcc_field.rb +3 -3
  33. data/lib/mail/fields/resent_cc_field.rb +3 -3
  34. data/lib/mail/fields/resent_date_field.rb +7 -7
  35. data/lib/mail/fields/resent_from_field.rb +3 -3
  36. data/lib/mail/fields/resent_message_id_field.rb +3 -3
  37. data/lib/mail/fields/resent_sender_field.rb +3 -3
  38. data/lib/mail/fields/resent_to_field.rb +3 -3
  39. data/lib/mail/fields/return_path_field.rb +3 -3
  40. data/lib/mail/fields/sender_field.rb +3 -3
  41. data/lib/mail/fields/structured_field.rb +12 -3
  42. data/lib/mail/fields/subject_field.rb +3 -2
  43. data/lib/mail/fields/to_field.rb +3 -3
  44. data/lib/mail/fields/unstructured_field.rb +68 -26
  45. data/lib/mail/header.rb +20 -4
  46. data/lib/mail/message.rb +36 -26
  47. data/lib/mail/parsers/content_disposition.rb +1 -1
  48. data/lib/mail/parsers/content_disposition.treetop +1 -1
  49. data/lib/mail/parsers/content_transfer_encoding.rb +1 -8
  50. data/lib/mail/parsers/content_transfer_encoding.treetop +1 -1
  51. data/lib/mail/parsers/content_type.rb +1 -1
  52. data/lib/mail/parsers/content_type.treetop +1 -1
  53. data/lib/mail/parsers/rfc2045.rb +6 -6
  54. data/lib/mail/parsers/rfc2045.treetop +1 -1
  55. data/lib/mail/parsers/rfc2822.rb +236 -236
  56. data/lib/mail/parsers/rfc2822.treetop +12 -12
  57. data/lib/mail/patterns.rb +6 -4
  58. data/lib/mail/utilities.rb +8 -5
  59. data/lib/mail/version.rb +3 -4
  60. metadata +16 -17
@@ -36,11 +36,11 @@ module Mail
36
36
  FIELD_NAME = 'resent-from'
37
37
  CAPITALIZED_FIELD = 'Resent-From'
38
38
 
39
- def initialize(*args)
40
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, args.last))
39
+ def initialize(value = nil, charset = 'utf-8')
40
+ self.charset = charset
41
+ super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
41
42
  self.parse
42
43
  self
43
-
44
44
  end
45
45
 
46
46
  def encoded
@@ -11,11 +11,11 @@ module Mail
11
11
  FIELD_NAME = 'resent-message-id'
12
12
  CAPITALIZED_FIELD = 'Resent-Message-ID'
13
13
 
14
- def initialize(*args)
15
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, args.last))
14
+ def initialize(value = nil, charset = 'utf-8')
15
+ self.charset = charset
16
+ super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
16
17
  self.parse
17
18
  self
18
-
19
19
  end
20
20
 
21
21
  def name
@@ -35,11 +35,11 @@ module Mail
35
35
  FIELD_NAME = 'resent-sender'
36
36
  CAPITALIZED_FIELD = 'Resent-Sender'
37
37
 
38
- def initialize(*args)
39
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, args.last))
38
+ def initialize(value = nil, charset = 'utf-8')
39
+ self.charset = charset
40
+ super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
40
41
  self.parse
41
42
  self
42
-
43
43
  end
44
44
 
45
45
  def addresses
@@ -36,11 +36,11 @@ module Mail
36
36
  FIELD_NAME = 'resent-to'
37
37
  CAPITALIZED_FIELD = 'Resent-To'
38
38
 
39
- def initialize(*args)
40
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, args.last))
39
+ def initialize(value = nil, charset = 'utf-8')
40
+ self.charset = charset
41
+ super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
41
42
  self.parse
42
43
  self
43
-
44
44
  end
45
45
 
46
46
  def encoded
@@ -37,11 +37,11 @@ module Mail
37
37
  FIELD_NAME = 'return-path'
38
38
  CAPITALIZED_FIELD = 'Return-Path'
39
39
 
40
- def initialize(*args)
41
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, args.last))
40
+ def initialize(value = nil, charset = 'utf-8')
41
+ self.charset = charset
42
+ super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
42
43
  self.parse
43
44
  self
44
-
45
45
  end
46
46
 
47
47
  def encoded
@@ -36,11 +36,11 @@ module Mail
36
36
  FIELD_NAME = 'sender'
37
37
  CAPITALIZED_FIELD = 'Sender'
38
38
 
39
- def initialize(*args)
40
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, args.last))
39
+ def initialize(value = nil, charset = 'utf-8')
40
+ self.charset = charset
41
+ super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
41
42
  self.parse
42
43
  self
43
-
44
44
  end
45
45
 
46
46
  def addresses
@@ -24,12 +24,21 @@ module Mail
24
24
  include Mail::CommonField
25
25
  include Mail::Utilities
26
26
 
27
- def initialize(*args)
28
- self.name = args.first
29
- self.value = args.last
27
+ def initialize(name = nil, value = nil, charset = nil)
28
+ self.name = name
29
+ self.value = value
30
+ self.charset = charset
30
31
  self
31
32
  end
32
33
 
34
+ def charset
35
+ @charset
36
+ end
37
+
38
+ def charset=(val)
39
+ @charset = val
40
+ end
41
+
33
42
  def default
34
43
  decoded
35
44
  end
@@ -7,8 +7,9 @@ module Mail
7
7
  FIELD_NAME = 'subject'
8
8
  CAPITALIZED_FIELD = "Subject"
9
9
 
10
- def initialize(*args)
11
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, args.last))
10
+ def initialize(value = nil, charset = 'utf-8')
11
+ self.charset = charset
12
+ super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
12
13
  end
13
14
 
14
15
  end
@@ -36,11 +36,11 @@ module Mail
36
36
  FIELD_NAME = 'to'
37
37
  CAPITALIZED_FIELD = 'To'
38
38
 
39
- def initialize(*args)
40
- super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, args.last))
39
+ def initialize(value = nil, charset = 'utf-8')
40
+ self.charset = charset
41
+ super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, value), charset)
41
42
  self.parse
42
43
  self
43
-
44
44
  end
45
45
 
46
46
  def encoded
@@ -19,13 +19,31 @@ module Mail
19
19
  include Mail::CommonField
20
20
  include Mail::Utilities
21
21
 
22
- def initialize(*args)
22
+ def initialize(name, value, charset = nil)
23
+ self.charset = charset
23
24
  @errors = []
24
- self.name = args.first
25
- self.value = args.last
25
+ if charset
26
+ self.charset = charset
27
+ else
28
+ if value.to_s.respond_to?(:encoding)
29
+ self.charset = value.to_s.encoding
30
+ else
31
+ self.charset = $KCODE
32
+ end
33
+ end
34
+ self.name = name
35
+ self.value = value
26
36
  self
27
37
  end
28
38
 
39
+ def charset
40
+ @charset
41
+ end
42
+
43
+ def charset=(val)
44
+ @charset = val
45
+ end
46
+
29
47
  def errors
30
48
  @errors
31
49
  end
@@ -83,41 +101,65 @@ module Mail
83
101
  # preference to other places where the field could be folded, even if
84
102
  # it is allowed elsewhere.
85
103
  def wrapped_value # :nodoc:
86
- case
87
- when decoded.to_s.ascii_only? && field_length <= 78
88
- "#{name}: #{value}"
89
- when field_length <= 40 # Allow for =?ISO-8859-1?B?=...=
90
- "#{name}: #{encode(value)}"
91
- else
92
- @folded_line = []
93
- @unfolded_line = value.clone
94
- fold("#{name}: ".length)
95
- folded = @folded_line.map { |l| l unless l.blank? }.compact.join("\r\n\t")
96
- "#{name}: #{folded}"
104
+ @folded_line = []
105
+ @unfolded_line = decoded.to_s.clone
106
+ fold("#{name}: ".length)
107
+ wrap_lines(name, @folded_line)
108
+ end
109
+
110
+ def wrap_lines(name, folded_lines)
111
+ result = []
112
+ index = 0
113
+ result[index] = "#{name}: #{folded_lines.shift}"
114
+ folded_lines.each do |line|
115
+ if (result[index] + line).length < 77
116
+ result[index] << " " + line
117
+ else
118
+ result[index] << "\r\n\t"
119
+ index += 1
120
+ result[index] = line
121
+ end
97
122
  end
123
+ result.join
98
124
  end
99
125
 
100
126
  def fold(prepend = 0) # :nodoc:
101
127
  # Get the last whitespace character, OR we'll just choose
102
- # 78 if there is no whitespace
103
- @unfolded_line.ascii_only? ? (limit = 78 - prepend) : (limit = 40 - prepend)
104
- wspp = @unfolded_line.slice(0..limit) =~ /[ \t][^ \T]*$/ || limit
105
- wspp = limit if wspp == 0
106
- @folded_line << encode(@unfolded_line.slice!(0...wspp))
107
- if @unfolded_line.length > limit
128
+ # 78 if there is no whitespace, or 23 for non ascii:
129
+ # Each QP byte is 6 chars (=0A)
130
+ # Plus 18 for the =?encoding?Q?= ... ?=
131
+ # Plus 2 for the \r\n and 1 for the \t
132
+ # 80 - 2 - 1 - 18 = 59 / 6 ~= 10
133
+ @unfolded_line.ascii_only? ? (limit = 78 - prepend) : (limit = 10 - prepend)
134
+ # find the last white space character within the limit
135
+ if wspp = @unfolded_line.mb_chars.slice(0..limit) =~ /[ \t][^ \t]*$/
136
+ wrap = true
137
+ wspp = limit if wspp == 0
138
+ @folded_line << encode(@unfolded_line.mb_chars.slice!(0...wspp).strip.to_str)
139
+ @folded_line.flatten!
140
+ # if no last whitespace before the limit, find the first
141
+ elsif wspp = @unfolded_line.mb_chars =~ /[ \t][^ \t]/
142
+ wrap = true
143
+ wspp = limit if wspp == 0
144
+ @folded_line << encode(@unfolded_line.mb_chars.slice!(0...wspp).strip.to_str)
145
+ @folded_line.flatten!
146
+ # if no whitespace, don't wrap
147
+ else
148
+ wrap = false
149
+ end
150
+
151
+ if wrap && @unfolded_line.length > limit
108
152
  fold
109
153
  else
110
154
  @folded_line << encode(@unfolded_line)
155
+ @folded_line.flatten!
111
156
  end
112
157
  end
113
158
 
114
159
  def encode(value)
115
- if value.ascii_only?
116
- value
117
- else
118
- RUBY_VERSION < '1.9' ? encoding = $KCODE : encoding = @value.encoding
119
- Encodings.b_value_encode(value, encoding)
120
- end
160
+ value.gsub!("\r", "=0D")
161
+ value.gsub!("\n", "=0A")
162
+ Encodings.q_value_encode(value, @charset).split(" ")
121
163
  end
122
164
 
123
165
  end
data/lib/mail/header.rb CHANGED
@@ -33,8 +33,9 @@ module Mail
33
33
  # no automatic processing of that field will happen. If you find one of
34
34
  # these cases, please make a patch and send it in, or at the least, send
35
35
  # me the example so we can fix it.
36
- def initialize(header_text = nil)
36
+ def initialize(header_text = nil, charset = nil)
37
37
  @errors = []
38
+ @charset = charset
38
39
  self.raw_source = header_text.to_crlf
39
40
  split_header if header_text
40
41
  end
@@ -74,7 +75,7 @@ module Mail
74
75
  @fields = Mail::FieldList.new
75
76
  unfolded_fields.each do |field|
76
77
 
77
- field = Field.new(field)
78
+ field = Field.new(field, nil, charset)
78
79
  field.errors.each { |error| self.errors << error }
79
80
  selected = select_field_for(field.name)
80
81
 
@@ -155,11 +156,26 @@ module Mail
155
156
  # User wants to create the field
156
157
  else
157
158
  # Need to insert in correct order for trace fields
158
- self.fields << Field.new(name.to_s, value)
159
+ self.fields << Field.new(name.to_s, value, charset)
159
160
  end
160
161
  end
161
162
 
162
- LIMITED_FIELDS = %w[ orig-date from sender reply-to to cc bcc
163
+ def charset
164
+ if self[:content_type] && self[:content_type].parameters
165
+ self[:content_type].parameters[:charset]
166
+ else
167
+ @charset
168
+ end
169
+ end
170
+
171
+ def charset=(val)
172
+ if self[:content_type]
173
+ self[:content_type].parameters[:charset] = val
174
+ end
175
+ @charset = val
176
+ end
177
+
178
+ LIMITED_FIELDS = %w[ date from sender reply-to to cc bcc
163
179
  message-id in-reply-to references subject
164
180
  return-path content-type mime-version
165
181
  content-transfer-encoding content-description
data/lib/mail/message.rb CHANGED
@@ -100,7 +100,10 @@ module Mail
100
100
  @text_part = nil
101
101
  @html_part = nil
102
102
  @errors = nil
103
-
103
+ @header = nil
104
+ @charset = 'UTF-8'
105
+ @defaulted_charset = true
106
+
104
107
  @perform_deliveries = true
105
108
  @raise_delivery_errors = true
106
109
 
@@ -109,13 +112,13 @@ module Mail
109
112
  @delivery_method = Mail.delivery_method.dup
110
113
 
111
114
  @transport_encoding = Mail::Encodings.get_encoding('7bit')
112
-
115
+
113
116
  if args.flatten.first.respond_to?(:each_pair)
114
117
  init_with_hash(args.flatten.first)
115
118
  else
116
119
  init_with_string(args.flatten[0].to_s.strip)
117
120
  end
118
-
121
+
119
122
  if block_given?
120
123
  instance_eval(&block)
121
124
  end
@@ -307,7 +310,8 @@ module Mail
307
310
  self_message_id, other_message_id = self.message_id, other.message_id
308
311
  self.message_id, other.message_id = '<temp@test>', '<temp@test>'
309
312
  result = self.encoded == other.encoded
310
- self.message_id, other.message_id = "<#{self_message_id}>", "<#{other_message_id}>"
313
+ self.message_id = "<#{self_message_id}>" if self_message_id
314
+ other.message_id = "<#{other_message_id}>" if other_message_id
311
315
  result
312
316
  end
313
317
  end
@@ -352,7 +356,7 @@ module Mail
352
356
  # mail.header = 'To: mikel@test.lindsaar.net\r\nFrom: Bob@bob.com'
353
357
  # mail.header #=> <#Mail::Header
354
358
  def header=(value)
355
- @header = Mail::Header.new(value)
359
+ @header = Mail::Header.new(value, charset)
356
360
  end
357
361
 
358
362
  # Returns the header object of the message object. Or, if passed
@@ -804,7 +808,7 @@ module Mail
804
808
  # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>'
805
809
  # mail.resent_cc #=> ['mikel@test.lindsaar.net']
806
810
  # mail.resent_cc = 'Mikel <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
807
- # mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
811
+ # mail.resent_cc #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
808
812
  def resent_cc=( val )
809
813
  header[:resent_cc] = val
810
814
  end
@@ -1270,18 +1274,17 @@ module Mail
1270
1274
  def has_mime_version?
1271
1275
  header.has_mime_version?
1272
1276
  end
1273
-
1277
+
1274
1278
  def has_content_type?
1275
1279
  !!header[:content_type]
1276
1280
  end
1277
1281
 
1278
1282
  def has_charset?
1279
- !!charset
1283
+ !!(header[:content_type] && header[:content_type].parameters['charset'])
1280
1284
  end
1281
1285
 
1282
1286
  def has_content_transfer_encoding?
1283
- header[:content_transfer_encoding] &&
1284
- header[:content_transfer_encoding].errors.blank?
1287
+ header[:content_transfer_encoding] && header[:content_transfer_encoding].errors.blank?
1285
1288
  end
1286
1289
 
1287
1290
  def has_transfer_encoding? # :nodoc:
@@ -1325,17 +1328,19 @@ module Mail
1325
1328
  def add_content_type
1326
1329
  header[:content_type] = 'text/plain'
1327
1330
  end
1328
-
1331
+
1329
1332
  # Adds a content type and charset if the body is US-ASCII
1330
1333
  #
1331
1334
  # Otherwise raises a warning
1332
1335
  def add_charset
1333
- if body.only_us_ascii? and !body.empty?
1334
- header[:content_type].parameters['charset'] = 'US-ASCII'
1335
- elsif !body.empty?
1336
- warning = "Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.\n"
1337
- STDERR.puts(warning)
1338
- header[:content_type].parameters['charset'] = 'UTF-8'
1336
+ if !body.empty?
1337
+ # Only give a warning if this isn't an attachment, has non US-ASCII and the user
1338
+ # has not specified an encoding explicitly.
1339
+ if @defaulted_charset && body.raw_source.not_ascii_only? && !self.attachment?
1340
+ warning = "Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.\n"
1341
+ STDERR.puts(warning)
1342
+ end
1343
+ header[:content_type].parameters['charset'] = @charset
1339
1344
  end
1340
1345
  end
1341
1346
 
@@ -1374,17 +1379,18 @@ module Mail
1374
1379
 
1375
1380
  # Returns the character set defined in the content type field
1376
1381
  def charset
1377
- content_type ? content_type_parameters['charset'] : nil
1382
+ if @header
1383
+ content_type ? content_type_parameters['charset'] : @charset
1384
+ else
1385
+ @charset
1386
+ end
1378
1387
  end
1379
1388
 
1380
- # Sets the charset to the supplied value. Will set the content type to text/plain if
1381
- # it does not already exist
1389
+ # Sets the charset to the supplied value.
1382
1390
  def charset=(value)
1383
- if content_type
1384
- content_type_parameters['charset'] = value
1385
- else
1386
- self.content_type ['text', 'plain', {'charset' => value}]
1387
- end
1391
+ @defaulted_charset = false
1392
+ @charset = value
1393
+ @header.charset = value
1388
1394
  end
1389
1395
 
1390
1396
  # Returns the main content type
@@ -1755,7 +1761,7 @@ module Mail
1755
1761
  end
1756
1762
 
1757
1763
  def identify_and_set_transfer_encoding
1758
- if body.multipart?
1764
+ if body && body.multipart?
1759
1765
  self.content_transfer_encoding = @transport_encoding
1760
1766
  else
1761
1767
  self.content_transfer_encoding = body.get_best_encoding(@transport_encoding)
@@ -1775,6 +1781,7 @@ module Mail
1775
1781
 
1776
1782
  def add_multipart_alternate_header
1777
1783
  header['content-type'] = ContentTypeField.with_boundary('multipart/alternative').value
1784
+ header['content_type'].parameters[:charset] = @charset
1778
1785
  body.boundary = boundary
1779
1786
  end
1780
1787
 
@@ -1782,6 +1789,7 @@ module Mail
1782
1789
  unless body.boundary && boundary
1783
1790
  header['content-type'] = 'multipart/mixed' unless header['content-type']
1784
1791
  header['content-type'].parameters[:boundary] = ContentTypeField.generate_boundary
1792
+ header['content_type'].parameters[:charset] = @charset
1785
1793
  body.boundary = boundary
1786
1794
  end
1787
1795
  end
@@ -1789,6 +1797,7 @@ module Mail
1789
1797
  def add_multipart_mixed_header
1790
1798
  unless header['content-type']
1791
1799
  header['content-type'] = ContentTypeField.with_boundary('multipart/mixed').value
1800
+ header['content_type'].parameters[:charset] = @charset
1792
1801
  body.boundary = boundary
1793
1802
  end
1794
1803
  end
@@ -1796,6 +1805,7 @@ module Mail
1796
1805
  def init_with_hash(hash)
1797
1806
  passed_in_options = hash.with_indifferent_access
1798
1807
  self.raw_source = ''
1808
+
1799
1809
  @header = Mail::Header.new
1800
1810
  @body = Mail::Body.new
1801
1811