mail 2.5.3 → 2.5.4

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 (66) hide show
  1. data/CHANGELOG.rdoc +65 -0
  2. data/CONTRIBUTING.md +4 -4
  3. data/Gemfile +7 -20
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +10 -9
  6. data/Rakefile +3 -20
  7. data/lib/VERSION +1 -1
  8. data/lib/mail.rb +0 -1
  9. data/lib/mail/attachments_list.rb +2 -2
  10. data/lib/mail/body.rb +4 -4
  11. data/lib/mail/check_delivery_params.rb +12 -22
  12. data/lib/mail/core_extensions/object.rb +8 -8
  13. data/lib/mail/core_extensions/smtp.rb +12 -13
  14. data/lib/mail/core_extensions/string.rb +4 -4
  15. data/lib/mail/elements/address.rb +13 -5
  16. data/lib/mail/elements/envelope_from_element.rb +15 -2
  17. data/lib/mail/encodings.rb +61 -28
  18. data/lib/mail/encodings/quoted_printable.rb +4 -3
  19. data/lib/mail/field.rb +10 -11
  20. data/lib/mail/fields/bcc_field.rb +2 -2
  21. data/lib/mail/fields/cc_field.rb +2 -2
  22. data/lib/mail/fields/comments_field.rb +1 -1
  23. data/lib/mail/fields/common/common_address.rb +13 -3
  24. data/lib/mail/fields/common/common_field.rb +4 -4
  25. data/lib/mail/fields/content_id_field.rb +1 -2
  26. data/lib/mail/fields/content_transfer_encoding_field.rb +2 -2
  27. data/lib/mail/fields/content_type_field.rb +1 -1
  28. data/lib/mail/fields/date_field.rb +1 -1
  29. data/lib/mail/fields/from_field.rb +2 -2
  30. data/lib/mail/fields/in_reply_to_field.rb +2 -1
  31. data/lib/mail/fields/message_id_field.rb +2 -3
  32. data/lib/mail/fields/references_field.rb +2 -1
  33. data/lib/mail/fields/reply_to_field.rb +2 -2
  34. data/lib/mail/fields/resent_bcc_field.rb +2 -2
  35. data/lib/mail/fields/resent_cc_field.rb +2 -2
  36. data/lib/mail/fields/resent_from_field.rb +2 -2
  37. data/lib/mail/fields/resent_sender_field.rb +2 -2
  38. data/lib/mail/fields/resent_to_field.rb +2 -2
  39. data/lib/mail/fields/sender_field.rb +7 -7
  40. data/lib/mail/fields/to_field.rb +2 -2
  41. data/lib/mail/fields/unstructured_field.rb +1 -1
  42. data/lib/mail/header.rb +5 -1
  43. data/lib/mail/message.rb +133 -37
  44. data/lib/mail/multibyte/chars.rb +2 -2
  45. data/lib/mail/multibyte/unicode.rb +5 -3
  46. data/lib/mail/network/delivery_methods/exim.rb +1 -6
  47. data/lib/mail/network/delivery_methods/file_delivery.rb +1 -1
  48. data/lib/mail/network/delivery_methods/sendmail.rb +32 -10
  49. data/lib/mail/network/delivery_methods/smtp.rb +35 -34
  50. data/lib/mail/network/delivery_methods/smtp_connection.rb +6 -6
  51. data/lib/mail/network/delivery_methods/test_mailer.rb +2 -2
  52. data/lib/mail/parsers/content_transfer_encoding.rb +81 -42
  53. data/lib/mail/parsers/content_transfer_encoding.treetop +4 -6
  54. data/lib/mail/parsers/content_type.rb +16 -12
  55. data/lib/mail/parsers/content_type.treetop +2 -2
  56. data/lib/mail/parsers/rfc2045.rb +12 -55
  57. data/lib/mail/parsers/rfc2045.treetop +1 -2
  58. data/lib/mail/parsers/rfc2822.rb +50 -50
  59. data/lib/mail/parsers/rfc2822.treetop +19 -21
  60. data/lib/mail/part.rb +6 -2
  61. data/lib/mail/patterns.rb +1 -0
  62. data/lib/mail/utilities.rb +25 -17
  63. data/lib/mail/version_specific/ruby_1_8.rb +5 -1
  64. data/lib/mail/version_specific/ruby_1_9.rb +46 -21
  65. metadata +57 -8
  66. data/lib/mail/core_extensions/shell_escape.rb +0 -56
@@ -5,7 +5,7 @@ module Mail
5
5
  include RFC2045
6
6
 
7
7
  rule content_type
8
- main_type "/" sub_type param_hashes:(CFWS ";"? parameter CFWS)* {
8
+ main_type "/" sub_type param_hashes:(CFWS ";"* parameter CFWS)* {
9
9
  def parameters
10
10
  param_hashes.elements.map do |param|
11
11
  param.parameter.param_hash
@@ -65,4 +65,4 @@ module Mail
65
65
  end
66
66
 
67
67
  end
68
- end
68
+ end
@@ -216,64 +216,21 @@ module Mail
216
216
  return cached
217
217
  end
218
218
 
219
- i0 = index
220
- if has_terminal?("7bit", false, index)
221
- r1 = instantiate_node(SyntaxNode,input, index...(index + 4))
222
- @index += 4
223
- else
224
- terminal_parse_failure("7bit")
225
- r1 = nil
226
- end
227
- if r1
228
- r0 = r1
229
- else
230
- if has_terminal?("8bit", false, index)
231
- r2 = instantiate_node(SyntaxNode,input, index...(index + 4))
232
- @index += 4
233
- else
234
- terminal_parse_failure("8bit")
235
- r2 = nil
236
- end
237
- if r2
238
- r0 = r2
219
+ s0, i0 = [], index
220
+ loop do
221
+ r1 = _nt_token
222
+ if r1
223
+ s0 << r1
239
224
  else
240
- if has_terminal?("binary", false, index)
241
- r3 = instantiate_node(SyntaxNode,input, index...(index + 6))
242
- @index += 6
243
- else
244
- terminal_parse_failure("binary")
245
- r3 = nil
246
- end
247
- if r3
248
- r0 = r3
249
- else
250
- if has_terminal?("quoted-printable", false, index)
251
- r4 = instantiate_node(SyntaxNode,input, index...(index + 16))
252
- @index += 16
253
- else
254
- terminal_parse_failure("quoted-printable")
255
- r4 = nil
256
- end
257
- if r4
258
- r0 = r4
259
- else
260
- if has_terminal?("base64", false, index)
261
- r5 = instantiate_node(SyntaxNode,input, index...(index + 6))
262
- @index += 6
263
- else
264
- terminal_parse_failure("base64")
265
- r5 = nil
266
- end
267
- if r5
268
- r0 = r5
269
- else
270
- @index = i0
271
- r0 = nil
272
- end
273
- end
274
- end
225
+ break
275
226
  end
276
227
  end
228
+ if s0.empty?
229
+ @index = i0
230
+ r0 = nil
231
+ else
232
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
233
+ end
277
234
 
278
235
  node_cache[:ietf_token][start_index] = r0
279
236
 
@@ -8,8 +8,7 @@ module Mail
8
8
  end
9
9
 
10
10
  rule ietf_token
11
- "7bit" / "8bit" / "binary" /
12
- "quoted-printable" / "base64"
11
+ token+
13
12
  end
14
13
 
15
14
  rule custom_x_token
@@ -2389,12 +2389,7 @@ module Mail
2389
2389
  break
2390
2390
  end
2391
2391
  end
2392
- if s4.empty?
2393
- @index = i4
2394
- r4 = nil
2395
- else
2396
- r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
2397
- end
2392
+ r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
2398
2393
  s0 << r4
2399
2394
  if r4
2400
2395
  r10 = _nt_FWS
@@ -2804,6 +2799,23 @@ module Mail
2804
2799
  r0
2805
2800
  end
2806
2801
 
2802
+ module Mailbox0
2803
+ def dig_comments(comments, elements)
2804
+ elements.each { |elem|
2805
+ if elem.respond_to?(:comment)
2806
+ comments << elem.comment
2807
+ end
2808
+ dig_comments(comments, elem.elements) if elem.elements
2809
+ }
2810
+ end
2811
+
2812
+ def comments
2813
+ comments = []
2814
+ dig_comments(comments, elements)
2815
+ comments
2816
+ end
2817
+ end
2818
+
2807
2819
  def _nt_mailbox
2808
2820
  start_index = index
2809
2821
  if node_cache[:mailbox].has_key?(index)
@@ -2819,10 +2831,12 @@ module Mail
2819
2831
  r1 = _nt_name_addr
2820
2832
  if r1
2821
2833
  r0 = r1
2834
+ r0.extend(Mailbox0)
2822
2835
  else
2823
2836
  r2 = _nt_addr_spec
2824
2837
  if r2
2825
2838
  r0 = r2
2839
+ r0.extend(Mailbox0)
2826
2840
  else
2827
2841
  @index = i0
2828
2842
  r0 = nil
@@ -2852,24 +2866,6 @@ module Mail
2852
2866
  end
2853
2867
  end
2854
2868
 
2855
- module Address1
2856
-
2857
- def dig_comments(comments, elements)
2858
- elements.each { |elem|
2859
- if elem.respond_to?(:comment)
2860
- comments << elem.comment
2861
- end
2862
- dig_comments(comments, elem.elements) if elem.elements
2863
- }
2864
- end
2865
-
2866
- def comments
2867
- comments = []
2868
- dig_comments(comments, elements)
2869
- comments
2870
- end
2871
- end
2872
-
2873
2869
  def _nt_address
2874
2870
  start_index = index
2875
2871
  if node_cache[:address].has_key?(index)
@@ -2888,7 +2884,6 @@ module Mail
2888
2884
  r0 = r1
2889
2885
  else
2890
2886
  r2 = _nt_mailbox
2891
- r2.extend(Address1)
2892
2887
  if r2
2893
2888
  r0 = r2
2894
2889
  else
@@ -4299,41 +4294,46 @@ module Mail
4299
4294
  end
4300
4295
  s0 << r1
4301
4296
  if r1
4302
- i3, s3 = index, []
4303
- r4 = _nt_name_val_pair
4304
- s3 << r4
4305
- if r4
4306
- s5, i5 = [], index
4297
+ i4, s4 = index, []
4298
+ r5 = _nt_name_val_pair
4299
+ s4 << r5
4300
+ if r5
4301
+ s6, i6 = [], index
4307
4302
  loop do
4308
- i6, s6 = index, []
4309
- r7 = _nt_CFWS
4310
- s6 << r7
4311
- if r7
4312
- r8 = _nt_name_val_pair
4313
- s6 << r8
4303
+ i7, s7 = index, []
4304
+ r8 = _nt_CFWS
4305
+ s7 << r8
4306
+ if r8
4307
+ r9 = _nt_name_val_pair
4308
+ s7 << r9
4314
4309
  end
4315
- if s6.last
4316
- r6 = instantiate_node(SyntaxNode,input, i6...index, s6)
4317
- r6.extend(NameValList0)
4310
+ if s7.last
4311
+ r7 = instantiate_node(SyntaxNode,input, i7...index, s7)
4312
+ r7.extend(NameValList0)
4318
4313
  else
4319
- @index = i6
4320
- r6 = nil
4314
+ @index = i7
4315
+ r7 = nil
4321
4316
  end
4322
- if r6
4323
- s5 << r6
4317
+ if r7
4318
+ s6 << r7
4324
4319
  else
4325
4320
  break
4326
4321
  end
4327
4322
  end
4328
- r5 = instantiate_node(SyntaxNode,input, i5...index, s5)
4329
- s3 << r5
4323
+ r6 = instantiate_node(SyntaxNode,input, i6...index, s6)
4324
+ s4 << r6
4330
4325
  end
4331
- if s3.last
4332
- r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
4333
- r3.extend(NameValList1)
4326
+ if s4.last
4327
+ r4 = instantiate_node(SyntaxNode,input, i4...index, s4)
4328
+ r4.extend(NameValList1)
4334
4329
  else
4335
- @index = i3
4336
- r3 = nil
4330
+ @index = i4
4331
+ r4 = nil
4332
+ end
4333
+ if r4
4334
+ r3 = r4
4335
+ else
4336
+ r3 = instantiate_node(SyntaxNode,input, index...index)
4337
4337
  end
4338
4338
  s0 << r3
4339
4339
  end
@@ -184,7 +184,7 @@ module Mail
184
184
  end
185
185
 
186
186
  rule quoted_string
187
- CFWS? DQUOTE quoted_content:(FWS? qcontent)+ FWS? DQUOTE CFWS?
187
+ CFWS? DQUOTE quoted_content:(FWS? qcontent)* FWS? DQUOTE CFWS?
188
188
  end
189
189
 
190
190
  rule qcontent
@@ -222,7 +222,22 @@ module Mail
222
222
  end
223
223
 
224
224
  rule mailbox
225
- name_addr / addr_spec
225
+ (name_addr / addr_spec) {
226
+ def dig_comments(comments, elements)
227
+ elements.each { |elem|
228
+ if elem.respond_to?(:comment)
229
+ comments << elem.comment
230
+ end
231
+ dig_comments(comments, elem.elements) if elem.elements
232
+ }
233
+ end
234
+
235
+ def comments
236
+ comments = []
237
+ dig_comments(comments, elements)
238
+ comments
239
+ end
240
+ }
226
241
  end
227
242
 
228
243
  rule address
@@ -244,24 +259,7 @@ module Mail
244
259
  end
245
260
 
246
261
  } /
247
- mailbox {
248
-
249
- def dig_comments(comments, elements)
250
- elements.each { |elem|
251
- if elem.respond_to?(:comment)
252
- comments << elem.comment
253
- end
254
- dig_comments(comments, elem.elements) if elem.elements
255
- }
256
- end
257
-
258
- def comments
259
- comments = []
260
- dig_comments(comments, elements)
261
- comments
262
- end
263
-
264
- }
262
+ mailbox
265
263
  end
266
264
 
267
265
  rule address_list
@@ -340,7 +338,7 @@ module Mail
340
338
  end
341
339
 
342
340
  rule name_val_list
343
- (CFWS)? (name_val_pair (CFWS name_val_pair)*)
341
+ (CFWS)? (name_val_pair (CFWS name_val_pair)*)?
344
342
  end
345
343
 
346
344
  rule name_val_pair
@@ -38,8 +38,12 @@ module Mail
38
38
  end
39
39
 
40
40
  def add_required_fields
41
- add_content_id unless has_content_id?
42
41
  super
42
+ add_content_id if !has_content_id? && inline?
43
+ end
44
+
45
+ def add_required_message_fields
46
+ # Override so we don't add Date, MIME-Version, or Message-ID.
43
47
  end
44
48
 
45
49
  def delivery_status_report_part?
@@ -113,4 +117,4 @@ module Mail
113
117
 
114
118
  end
115
119
 
116
- end
120
+ end
@@ -22,6 +22,7 @@ module Mail
22
22
  FIELD_NAME = /[#{field_name}]+/
23
23
  FIELD_BODY = /.+/
24
24
  FIELD_LINE = /^[#{field_name}]+:\s*.+$/
25
+ FIELD_SPLIT = /^(#{FIELD_NAME})\s*:\s*(#{FIELD_BODY})?$/
25
26
  HEADER_LINE = /^([#{field_name}]+:\s*.+)$/
26
27
 
27
28
  QP_UNSAFE = /[^#{qp_safe}]/
@@ -41,28 +41,36 @@ module Mail
41
41
  token_safe?( str ) ? str : dquote(str)
42
42
  end
43
43
 
44
- # Wraps supplied string in double quotes unless it is already wrapped.
45
- #
46
- # Additionally will escape any double quotation marks in the string with a single
47
- # backslash in front of the '"' character.
44
+ # Wraps supplied string in double quotes and applies \-escaping as necessary,
45
+ # unless it is already wrapped.
46
+ #
47
+ # Example:
48
+ #
49
+ # string = 'This is a string'
50
+ # dquote(string) #=> '"This is a string"'
51
+ #
52
+ # string = 'This is "a string"'
53
+ # dquote(string #=> '"This is \"a string\"'
48
54
  def dquote( str )
49
- match = str.match(/^"(.*)?"$/)
50
- str = match[1] if match
51
- # First remove all escaped double quotes:
52
- str = str.gsub(/\\"/, '"')
53
- # Then wrap and re-escape all double quotes
54
- '"' + str.gsub(/["]/n) {|s| '\\' + s } + '"'
55
+ '"' + unquote(str).gsub(/[\\"]/n) {|s| '\\' + s } + '"'
55
56
  end
56
-
57
- # Unwraps supplied string from inside double quotes.
58
- #
57
+
58
+ # Unwraps supplied string from inside double quotes and
59
+ # removes any \-escaping.
60
+ #
59
61
  # Example:
60
- #
62
+ #
61
63
  # string = '"This is a string"'
62
64
  # unquote(string) #=> 'This is a string'
65
+ #
66
+ # string = '"This is \"a string\""'
67
+ # unqoute(string) #=> 'This is "a string"'
63
68
  def unquote( str )
64
- match = str.match(/^"(.*?)"$/)
65
- match ? match[1] : str
69
+ if str =~ /^"(.*?)"$/
70
+ $1.gsub(/\\(.)/, '\1')
71
+ else
72
+ str
73
+ end
66
74
  end
67
75
 
68
76
  # Wraps a string in parenthesis and escapes any that are in the string itself.
@@ -135,7 +143,7 @@ module Mail
135
143
  # obj1 = :this_IS_an_object
136
144
  # match_to_s( obj1, obj2 ) #=> true
137
145
  def match_to_s( obj1, obj2 )
138
- obj1.to_s.downcase == obj2.to_s.downcase
146
+ obj1.to_s.casecmp(obj2.to_s) == 0
139
147
  end
140
148
 
141
149
  # Capitalizes a string that is joined by hyphens correctly.
@@ -58,7 +58,7 @@ module Mail
58
58
  # Ruby 1.8 requires an encoding to work
59
59
  raise ArgumentError, "Must supply an encoding" if encoding.nil?
60
60
  encoding = encoding.to_s.upcase.gsub('_', '-')
61
- [Encodings::Base64.encode(str), encoding]
61
+ [Encodings::Base64.encode(str), fix_encoding(encoding)]
62
62
  end
63
63
 
64
64
  def Ruby18.b_value_decode(str)
@@ -107,6 +107,10 @@ module Mail
107
107
  case encoding.upcase
108
108
  when 'UTF8'
109
109
  'UTF-8'
110
+ when 'UTF16', 'UTF-16'
111
+ 'UTF-16BE'
112
+ when 'UTF32', 'UTF-32'
113
+ 'UTF-32BE'
110
114
  else
111
115
  encoding
112
116
  end
@@ -51,9 +51,9 @@ module Mail
51
51
  def Ruby19.b_value_decode(str)
52
52
  match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m)
53
53
  if match
54
- encoding = match[1]
54
+ charset = match[1]
55
55
  str = Ruby19.decode_base64(match[2])
56
- str.force_encoding(fix_encoding(encoding))
56
+ str.force_encoding(pick_encoding(charset))
57
57
  end
58
58
  decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
59
59
  decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
@@ -67,12 +67,12 @@ module Mail
67
67
  def Ruby19.q_value_decode(str)
68
68
  match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m)
69
69
  if match
70
- encoding = match[1]
70
+ charset = match[1]
71
71
  string = match[2].gsub(/_/, '=20')
72
72
  # Remove trailing = if it exists in a Q encoding
73
73
  string = string.sub(/\=$/, '')
74
74
  str = Encodings::QuotedPrintable.decode(string)
75
- str.force_encoding(fix_encoding(encoding))
75
+ str.force_encoding(pick_encoding(charset))
76
76
  end
77
77
  decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
78
78
  decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
@@ -96,23 +96,48 @@ module Mail
96
96
  @uri_parser ||= URI::Parser.new
97
97
  end
98
98
 
99
- # mails somtimes includes invalid encodings like iso885915 or utf8 so we transform them to iso885915 or utf8
100
- # TODO: add this as a test somewhere
101
- # Encoding.list.map{|e| [e.to_s.upcase==fix_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
102
- # Encoding.list.map{|e| [e.to_s==fix_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
103
- def Ruby19.fix_encoding(encoding)
104
- case encoding
105
- # ISO-8859-15, ISO-2022-JP and alike
106
- when /iso-?(\d{4})-?(\w{1,2})/i then return "ISO-#{$1}-#{$2}"
107
- # "ISO-2022-JP-KDDI" and alike
108
- when /iso-?(\d{4})-?(\w{1,2})-?(\w*)/i then return "ISO-#{$1}-#{$2}-#{$3}"
109
- # UTF-8, UTF-32BE and alike
110
- when /utf-?(\d{1,2})?(\w{1,2})/i then return "UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
111
- # Windows-1252 and alike
112
- when /Windows-?(.*)/i then return "Windows-#{$1}"
113
- when /^8bit$/ then return "ASCII-8BIT"
114
- #more aliases to be added if needed
115
- else return encoding
99
+ # Pick a Ruby encoding corresponding to the message charset. Most
100
+ # charsets have a Ruby encoding, but some need manual aliasing here.
101
+ #
102
+ # TODO: add this as a test somewhere:
103
+ # Encoding.list.map { |e| [e.to_s.upcase == pick_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
104
+ # Encoding.list.map { |e| [e.to_s == pick_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
105
+ def Ruby19.pick_encoding(charset)
106
+ case charset
107
+
108
+ # ISO-8859-15, ISO-2022-JP and alike
109
+ when /iso-?(\d{4})-?(\w{1,2})/i
110
+ "ISO-#{$1}-#{$2}"
111
+
112
+ # "ISO-2022-JP-KDDI" and alike
113
+ when /iso-?(\d{4})-?(\w{1,2})-?(\w*)/i
114
+ "ISO-#{$1}-#{$2}-#{$3}"
115
+
116
+ # UTF-8, UTF-32BE and alike
117
+ when /utf-?(\d{1,2})?(\w{1,2})/i
118
+ "UTF-#{$1}#{$2}".gsub(/\A(UTF-(?:16|32))\z/, '\\1BE')
119
+
120
+ # Windows-1252 and alike
121
+ when /Windows-?(.*)/i
122
+ "Windows-#{$1}"
123
+
124
+ when /^8bit$/
125
+ Encoding::ASCII_8BIT
126
+
127
+ # Microsoft-specific alias for CP949 (Korean)
128
+ when 'ks_c_5601-1987'
129
+ Encoding::CP949
130
+
131
+ # Wrongly written Shift_JIS (Japanese)
132
+ when 'shift-jis'
133
+ Encoding::Shift_JIS
134
+
135
+ # GB2312 (Chinese charset) is a subset of GB18030 (its replacement)
136
+ when /gb2312/i
137
+ Encoding::GB18030
138
+
139
+ else
140
+ charset
116
141
  end
117
142
  end
118
143
  end