mail 2.4.4 → 2.6.3

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.
Files changed (163) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.rdoc +156 -0
  3. data/CONTRIBUTING.md +21 -6
  4. data/Dependencies.txt +0 -1
  5. data/Gemfile +10 -21
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +52 -32
  8. data/Rakefile +11 -22
  9. data/lib/mail/attachments_list.rb +2 -2
  10. data/lib/mail/body.rb +33 -9
  11. data/lib/mail/check_delivery_params.rb +20 -0
  12. data/lib/mail/configuration.rb +1 -1
  13. data/lib/mail/{patterns.rb → constants.rb} +26 -6
  14. data/lib/mail/core_extensions/nil.rb +4 -2
  15. data/lib/mail/core_extensions/object.rb +8 -8
  16. data/lib/mail/core_extensions/smtp.rb +12 -13
  17. data/lib/mail/core_extensions/string.rb +19 -6
  18. data/lib/mail/elements/address.rb +82 -141
  19. data/lib/mail/elements/address_list.rb +9 -42
  20. data/lib/mail/elements/content_disposition_element.rb +3 -7
  21. data/lib/mail/elements/content_location_element.rb +2 -6
  22. data/lib/mail/elements/content_transfer_encoding_element.rb +3 -10
  23. data/lib/mail/elements/content_type_element.rb +4 -8
  24. data/lib/mail/elements/date_time_element.rb +3 -7
  25. data/lib/mail/elements/envelope_from_element.rb +18 -13
  26. data/lib/mail/elements/message_ids_element.rb +1 -6
  27. data/lib/mail/elements/mime_version_element.rb +3 -7
  28. data/lib/mail/elements/phrase_list.rb +2 -7
  29. data/lib/mail/elements/received_element.rb +3 -7
  30. data/lib/mail/elements.rb +12 -12
  31. data/lib/mail/encodings/quoted_printable.rb +4 -3
  32. data/lib/mail/encodings/unix_to_unix.rb +17 -0
  33. data/lib/mail/encodings.rb +53 -38
  34. data/lib/mail/envelope.rb +0 -5
  35. data/lib/mail/field.rb +131 -118
  36. data/lib/mail/field_list.rb +18 -18
  37. data/lib/mail/fields/bcc_field.rb +2 -2
  38. data/lib/mail/fields/cc_field.rb +2 -2
  39. data/lib/mail/fields/comments_field.rb +1 -1
  40. data/lib/mail/fields/common/common_address.rb +32 -22
  41. data/lib/mail/fields/common/common_date.rb +0 -7
  42. data/lib/mail/fields/common/common_field.rb +10 -3
  43. data/lib/mail/fields/common/common_message_id.rb +9 -5
  44. data/lib/mail/fields/common/parameter_hash.rb +3 -3
  45. data/lib/mail/fields/content_disposition_field.rb +1 -0
  46. data/lib/mail/fields/content_id_field.rb +1 -2
  47. data/lib/mail/fields/content_transfer_encoding_field.rb +2 -8
  48. data/lib/mail/fields/content_type_field.rb +5 -2
  49. data/lib/mail/fields/date_field.rb +14 -14
  50. data/lib/mail/fields/from_field.rb +2 -2
  51. data/lib/mail/fields/in_reply_to_field.rb +2 -1
  52. data/lib/mail/fields/keywords_field.rb +1 -1
  53. data/lib/mail/fields/message_id_field.rb +2 -3
  54. data/lib/mail/fields/references_field.rb +2 -1
  55. data/lib/mail/fields/reply_to_field.rb +2 -2
  56. data/lib/mail/fields/resent_bcc_field.rb +2 -2
  57. data/lib/mail/fields/resent_cc_field.rb +2 -2
  58. data/lib/mail/fields/resent_from_field.rb +2 -2
  59. data/lib/mail/fields/resent_sender_field.rb +3 -3
  60. data/lib/mail/fields/resent_to_field.rb +2 -2
  61. data/lib/mail/fields/sender_field.rb +8 -8
  62. data/lib/mail/fields/to_field.rb +2 -2
  63. data/lib/mail/fields/unstructured_field.rb +44 -31
  64. data/lib/mail/fields.rb +32 -32
  65. data/lib/mail/header.rb +47 -37
  66. data/lib/mail/mail.rb +15 -1
  67. data/lib/mail/matchers/has_sent_mail.rb +36 -3
  68. data/lib/mail/message.rb +164 -57
  69. data/lib/mail/multibyte/chars.rb +4 -4
  70. data/lib/mail/multibyte/unicode.rb +9 -1
  71. data/lib/mail/network/delivery_methods/exim.rb +2 -3
  72. data/lib/mail/network/delivery_methods/file_delivery.rb +5 -0
  73. data/lib/mail/network/delivery_methods/sendmail.rb +35 -8
  74. data/lib/mail/network/delivery_methods/smtp.rb +34 -45
  75. data/lib/mail/network/delivery_methods/smtp_connection.rb +9 -22
  76. data/lib/mail/network/delivery_methods/test_mailer.rb +6 -2
  77. data/lib/mail/network/retriever_methods/imap.rb +32 -19
  78. data/lib/mail/network/retriever_methods/pop3.rb +2 -2
  79. data/lib/mail/network/retriever_methods/test_retriever.rb +11 -15
  80. data/lib/mail/network.rb +9 -9
  81. data/lib/mail/parsers/address_lists_parser.rb +132 -0
  82. data/lib/mail/parsers/content_disposition_parser.rb +67 -0
  83. data/lib/mail/parsers/content_location_parser.rb +35 -0
  84. data/lib/mail/parsers/content_transfer_encoding_parser.rb +33 -0
  85. data/lib/mail/parsers/content_type_parser.rb +66 -0
  86. data/lib/mail/parsers/date_time_parser.rb +36 -0
  87. data/lib/mail/parsers/envelope_from_parser.rb +45 -0
  88. data/lib/mail/parsers/message_ids_parser.rb +39 -0
  89. data/lib/mail/parsers/mime_version_parser.rb +41 -0
  90. data/lib/mail/parsers/phrase_lists_parser.rb +33 -0
  91. data/lib/mail/parsers/ragel/common.rl +185 -0
  92. data/lib/mail/parsers/ragel/date_time.rl +30 -0
  93. data/lib/mail/parsers/ragel/parser_info.rb +61 -0
  94. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb +14864 -0
  95. data/lib/mail/parsers/ragel/ruby/machines/address_lists_machine.rb.rl +37 -0
  96. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb +751 -0
  97. data/lib/mail/parsers/ragel/ruby/machines/content_disposition_machine.rb.rl +37 -0
  98. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb +614 -0
  99. data/lib/mail/parsers/ragel/ruby/machines/content_location_machine.rb.rl +37 -0
  100. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb +447 -0
  101. data/lib/mail/parsers/ragel/ruby/machines/content_transfer_encoding_machine.rb.rl +37 -0
  102. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb +825 -0
  103. data/lib/mail/parsers/ragel/ruby/machines/content_type_machine.rb.rl +37 -0
  104. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb +817 -0
  105. data/lib/mail/parsers/ragel/ruby/machines/date_time_machine.rb.rl +37 -0
  106. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb +2149 -0
  107. data/lib/mail/parsers/ragel/ruby/machines/envelope_from_machine.rb.rl +37 -0
  108. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb +1570 -0
  109. data/lib/mail/parsers/ragel/ruby/machines/message_ids_machine.rb.rl +37 -0
  110. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb +440 -0
  111. data/lib/mail/parsers/ragel/ruby/machines/mime_version_machine.rb.rl +37 -0
  112. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb +564 -0
  113. data/lib/mail/parsers/ragel/ruby/machines/phrase_lists_machine.rb.rl +37 -0
  114. data/lib/mail/parsers/ragel/ruby/machines/rb_actions.rl +51 -0
  115. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb +5144 -0
  116. data/lib/mail/parsers/ragel/ruby/machines/received_machine.rb.rl +37 -0
  117. data/lib/mail/parsers/ragel/ruby/parser.rb.rl.erb +37 -0
  118. data/lib/mail/parsers/ragel/ruby.rb +40 -0
  119. data/lib/mail/parsers/ragel.rb +17 -0
  120. data/lib/mail/parsers/received_parser.rb +47 -0
  121. data/lib/mail/parsers.rb +26 -0
  122. data/lib/mail/part.rb +6 -2
  123. data/lib/mail/parts_list.rb +5 -3
  124. data/lib/mail/utilities.rb +70 -60
  125. data/lib/mail/values/unicode_tables.dat +0 -0
  126. data/lib/mail/version.rb +7 -15
  127. data/lib/mail/version_specific/ruby_1_8.rb +25 -4
  128. data/lib/mail/version_specific/ruby_1_9.rb +110 -30
  129. data/lib/mail.rb +20 -19
  130. metadata +129 -73
  131. data/Gemfile.lock +0 -36
  132. data/lib/VERSION +0 -4
  133. data/lib/mail/core_extensions/shell_escape.rb +0 -56
  134. data/lib/mail/parsers/address_lists.rb +0 -64
  135. data/lib/mail/parsers/address_lists.treetop +0 -19
  136. data/lib/mail/parsers/content_disposition.rb +0 -535
  137. data/lib/mail/parsers/content_disposition.treetop +0 -46
  138. data/lib/mail/parsers/content_location.rb +0 -139
  139. data/lib/mail/parsers/content_location.treetop +0 -20
  140. data/lib/mail/parsers/content_transfer_encoding.rb +0 -162
  141. data/lib/mail/parsers/content_transfer_encoding.treetop +0 -20
  142. data/lib/mail/parsers/content_type.rb +0 -967
  143. data/lib/mail/parsers/content_type.treetop +0 -68
  144. data/lib/mail/parsers/date_time.rb +0 -114
  145. data/lib/mail/parsers/date_time.treetop +0 -11
  146. data/lib/mail/parsers/envelope_from.rb +0 -194
  147. data/lib/mail/parsers/envelope_from.treetop +0 -32
  148. data/lib/mail/parsers/message_ids.rb +0 -45
  149. data/lib/mail/parsers/message_ids.treetop +0 -15
  150. data/lib/mail/parsers/mime_version.rb +0 -144
  151. data/lib/mail/parsers/mime_version.treetop +0 -19
  152. data/lib/mail/parsers/phrase_lists.rb +0 -45
  153. data/lib/mail/parsers/phrase_lists.treetop +0 -15
  154. data/lib/mail/parsers/received.rb +0 -71
  155. data/lib/mail/parsers/received.treetop +0 -11
  156. data/lib/mail/parsers/rfc2045.rb +0 -464
  157. data/lib/mail/parsers/rfc2045.treetop +0 -36
  158. data/lib/mail/parsers/rfc2822.rb +0 -5341
  159. data/lib/mail/parsers/rfc2822.treetop +0 -410
  160. data/lib/mail/parsers/rfc2822_obsolete.rb +0 -3768
  161. data/lib/mail/parsers/rfc2822_obsolete.treetop +0 -241
  162. data/lib/tasks/corpus.rake +0 -125
  163. data/lib/tasks/treetop.rake +0 -10
data/lib/mail/header.rb CHANGED
@@ -17,10 +17,24 @@ module Mail
17
17
  # 2.2.3. All field bodies MUST conform to the syntax described in
18
18
  # sections 3 and 4 of this standard.
19
19
  class Header
20
- include Patterns
20
+ include Constants
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
@@ -34,11 +48,15 @@ module Mail
34
48
  # these cases, please make a patch and send it in, or at the least, send
35
49
  # me the example so we can fix it.
36
50
  def initialize(header_text = nil, charset = nil)
37
- @errors = []
38
51
  @charset = charset
39
- self.raw_source = header_text.to_crlf
52
+ self.raw_source = header_text.to_crlf.lstrip
40
53
  split_header if header_text
41
54
  end
55
+
56
+ def initialize_copy(original)
57
+ super
58
+ @fields = @fields.dup
59
+ end
42
60
 
43
61
  # The preserved raw source of the header as you passed it in, untouched
44
62
  # for your Regexing glory.
@@ -73,14 +91,11 @@ module Mail
73
91
  # h.fields = ['From: mikel@me.com', 'To: bob@you.com']
74
92
  def fields=(unfolded_fields)
75
93
  @fields = Mail::FieldList.new
76
- warn "Warning: more than 1000 header fields only using the first 1000" if unfolded_fields.length > 1000
77
- unfolded_fields[0..1000].each do |field|
94
+ 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
95
+ unfolded_fields[0..(self.class.maximum_amount-1)].each do |field|
78
96
 
79
97
  field = Field.new(field, nil, charset)
80
- field.errors.each { |error| self.errors << error }
81
- selected = select_field_for(field.name)
82
-
83
- if selected.any? && limited_field?(field.name)
98
+ if limited_field?(field.name) && (selected = select_field_for(field.name)) && selected.any?
84
99
  selected.first.update(field.name, field.value)
85
100
  else
86
101
  @fields << field
@@ -90,7 +105,7 @@ module Mail
90
105
  end
91
106
 
92
107
  def errors
93
- @errors
108
+ @fields.map(&:errors).flatten(1)
94
109
  end
95
110
 
96
111
  # 3.6. Field definitions
@@ -115,7 +130,8 @@ module Mail
115
130
  # h['To'] #=> 'mikel@me.com'
116
131
  # h['X-Mail-SPAM'] #=> ['15', '20']
117
132
  def [](name)
118
- name = dasherize(name).downcase
133
+ name = dasherize(name)
134
+ name.downcase!
119
135
  selected = select_field_for(name)
120
136
  case
121
137
  when selected.length > 1
@@ -142,6 +158,9 @@ module Mail
142
158
  # h['X-Mail-SPAM'] # => nil
143
159
  def []=(name, value)
144
160
  name = dasherize(name)
161
+ if name.include?(':')
162
+ raise ArgumentError, "Header names may not contain a colon: #{name.inspect}"
163
+ end
145
164
  fn = name.downcase
146
165
  selected = select_field_for(fn)
147
166
 
@@ -159,15 +178,15 @@ module Mail
159
178
  # Need to insert in correct order for trace fields
160
179
  self.fields << Field.new(name.to_s, value, charset)
161
180
  end
181
+ if dasherize(fn) == "content-type"
182
+ # Update charset if specified in Content-Type
183
+ params = self[:content_type].parameters rescue nil
184
+ @charset = params[:charset] if params && params[:charset]
185
+ end
162
186
  end
163
187
 
164
188
  def charset
165
- params = self[:content_type].parameters rescue nil
166
- if params
167
- params[:charset]
168
- else
169
- @charset
170
- end
189
+ @charset
171
190
  end
172
191
 
173
192
  def charset=(val)
@@ -186,6 +205,7 @@ module Mail
186
205
 
187
206
  def encoded
188
207
  buffer = ''
208
+ buffer.force_encoding('us-ascii') if buffer.respond_to?(:force_encoding)
189
209
  fields.each do |field|
190
210
  buffer << field.encoded
191
211
  end
@@ -230,36 +250,26 @@ module Mail
230
250
  @raw_source = val
231
251
  end
232
252
 
233
- # 2.2.3. Long Header Fields
234
- #
235
- # The process of moving from this folded multiple-line representation
236
- # of a header field to its single line representation is called
237
- # "unfolding". Unfolding is accomplished by simply removing any CRLF
238
- # that is immediately followed by WSP. Each header field should be
239
- # treated in its unfolded form for further syntactic and semantic
240
- # evaluation.
241
- def unfold(string)
242
- string.gsub(/#{CRLF}#{WSP}+/, ' ').gsub(/#{WSP}+/, ' ')
243
- end
244
-
245
- # Returns the header with all the folds removed
246
- def unfolded_header
247
- @unfolded_header ||= unfold(raw_source)
248
- end
249
-
250
253
  # Splits an unfolded and line break cleaned header into individual field
251
254
  # strings.
252
255
  def split_header
253
- self.fields = unfolded_header.split(CRLF)
256
+ self.fields = raw_source.split(HEADER_SPLIT)
254
257
  end
255
258
 
256
259
  def select_field_for(name)
257
- fields.select { |f| f.responsible_for?(name.to_s) }
260
+ fields.select { |f| f.responsible_for?(name) }
258
261
  end
259
262
 
260
263
  def limited_field?(name)
261
264
  LIMITED_FIELDS.include?(name.to_s.downcase)
262
265
  end
263
-
266
+
267
+ # Enumerable support; yield each field in order to the block if there is one,
268
+ # or return an Enumerator for them if there isn't.
269
+ def each( &block )
270
+ return self.fields.each( &block ) if block
271
+ self.fields.each
272
+ end
273
+
264
274
  end
265
275
  end
data/lib/mail/mail.rb CHANGED
@@ -206,6 +206,12 @@ module Mail
206
206
  end
207
207
  end
208
208
 
209
+ # Unregister the given observer, allowing mail to resume operations
210
+ # without it.
211
+ def self.unregister_observer(observer)
212
+ @@delivery_notification_observers.delete(observer)
213
+ end
214
+
209
215
  # You can register an object to be given every mail object that will be sent,
210
216
  # before it is sent. So if you want to add special headers or modify any
211
217
  # email that gets sent through the Mail library, you can do so.
@@ -219,6 +225,12 @@ module Mail
219
225
  end
220
226
  end
221
227
 
228
+ # Unregister the given interceptor, allowing mail to resume operations
229
+ # without it.
230
+ def self.unregister_interceptor(interceptor)
231
+ @@delivery_interceptors.delete(interceptor)
232
+ end
233
+
222
234
  def self.inform_observers(mail)
223
235
  @@delivery_notification_observers.each do |observer|
224
236
  observer.delivered_email(mail)
@@ -233,9 +245,11 @@ module Mail
233
245
 
234
246
  protected
235
247
 
248
+ RANDOM_TAG='%x%x_%x%x%d%x'
249
+
236
250
  def self.random_tag
237
251
  t = Time.now
238
- sprintf('%x%x_%x%x%d%x',
252
+ sprintf(RANDOM_TAG,
239
253
  t.to_i, t.tv_usec,
240
254
  $$, Thread.current.object_id.abs, self.uniq, rand(255))
241
255
  end
@@ -29,6 +29,29 @@ module Mail
29
29
  self
30
30
  end
31
31
 
32
+ def cc(recipient_or_list)
33
+ @copy_recipients ||= []
34
+
35
+ if recipient_or_list.kind_of?(Array)
36
+ @copy_recipients += recipient_or_list
37
+ else
38
+ @copy_recipients << recipient_or_list
39
+ end
40
+ self
41
+ end
42
+
43
+ def bcc(recipient_or_list)
44
+ @blind_copy_recipients ||= []
45
+
46
+ if recipient_or_list.kind_of?(Array)
47
+ @blind_copy_recipients += recipient_or_list
48
+ else
49
+ @blind_copy_recipients << recipient_or_list
50
+ end
51
+ self
52
+ end
53
+
54
+
32
55
  def with_subject(subject)
33
56
  @subject = subject
34
57
  self
@@ -61,7 +84,7 @@ module Mail
61
84
  result
62
85
  end
63
86
 
64
- def negative_failure_message
87
+ def failure_message_when_negated
65
88
  result = "Expected no email to be sent "
66
89
  result += explain_expectations
67
90
  result += dump_deliveries
@@ -69,11 +92,11 @@ module Mail
69
92
  end
70
93
 
71
94
  protected
72
-
95
+
73
96
  def filter_matched_deliveries(deliveries)
74
97
  candidate_deliveries = deliveries
75
98
 
76
- %w(sender recipients subject subject_matcher body body_matcher).each do |modifier_name|
99
+ %w(sender recipients copy_recipients blind_copy_recipients subject subject_matcher body body_matcher).each do |modifier_name|
77
100
  next unless instance_variable_defined?("@#{modifier_name}")
78
101
  candidate_deliveries = candidate_deliveries.select{|matching_delivery| self.send("matches_on_#{modifier_name}?", matching_delivery)}
79
102
  end
@@ -89,6 +112,14 @@ module Mail
89
112
  @recipients.all? {|recipient| delivery.to.include?(recipient) }
90
113
  end
91
114
 
115
+ def matches_on_copy_recipients?(delivery)
116
+ @copy_recipients.all? {|recipient| delivery.cc.include?(recipient) }
117
+ end
118
+
119
+ def matches_on_blind_copy_recipients?(delivery)
120
+ @blind_copy_recipients.all? {|recipient| delivery.bcc.include?(recipient) }
121
+ end
122
+
92
123
  def matches_on_subject?(delivery)
93
124
  delivery.subject == @subject
94
125
  end
@@ -109,6 +140,8 @@ module Mail
109
140
  result = ''
110
141
  result += "from #{@sender} " if instance_variable_defined?('@sender')
111
142
  result += "to #{@recipients.inspect} " if instance_variable_defined?('@recipients')
143
+ result += "cc #{@copy_recipients.inspect} " if instance_variable_defined?('@copy_recipients')
144
+ result += "bcc #{@blind_copy_recipients.inspect} " if instance_variable_defined?('@blind_copy_recipients')
112
145
  result += "with subject \"#{@subject}\" " if instance_variable_defined?('@subject')
113
146
  result += "with subject matching \"#{@subject_matcher}\" " if instance_variable_defined?('@subject_matcher')
114
147
  result += "with body \"#{@body}\" " if instance_variable_defined?('@body')