mail 2.6.1 → 2.6.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b36c7dad6398232438e90270f9814eb04aeda138
4
- data.tar.gz: 7dbe4c27bd7cab8569892fc5d6a676718aaf755b
3
+ metadata.gz: 3af499471587d4cb917ab992eb4bb45f19b9579e
4
+ data.tar.gz: f05e6aaf887b9a79b9252e7edc2dd74b81f7cb6c
5
5
  SHA512:
6
- metadata.gz: 0628c04e7acf3cf0c3a801aff33a6008fe338ebd9a96a1d2b290ffbd98af1c287c0bdd958eda0ec77f495f498880dd69aaf8325da77e2f7d6ddb9f50396b1635
7
- data.tar.gz: 1d813d0f8b64feb0a5a0561e7d031d795b54bf3d0160c02fcd35a9f11e04875dd6ea05a67b5a565b3d67486ce8941a14ac2d1362a288032eb3863641c533fece
6
+ metadata.gz: 75bb13148f71ffbbef303ca23f01b0c6c8a14aeea255bd528ba3ac0545c056b75ff3a420dc0578c7f15efd894a3367072496121c8479aa8ddc78e376c115168f
7
+ data.tar.gz: 0f26c569a7c210d3ee57d95cf5011f8ff4941f5c1cd054ef804f018aa829bec44db3fdde7ab2fe1a89056cf81a5e6d618735caaaf9ccf4e8810e4295256f012e
@@ -1,10 +1,17 @@
1
1
  == HEAD
2
2
 
3
- Features:
3
+ == Version 2.6.3 - Mon Nov 3 23:53 +1100 2014 Mikel Lindsaar <mikel@reinteractive.net>
4
+
5
+ * #796 support uu encoding (grosser)
6
+
7
+ == Version 2.6.2 (Unreleased) - Wed Oct 22 13:42 -0500 2014 Benjamin Fleischer <github@benjaminfleischer.com>
4
8
 
5
9
  Performance:
10
+ * #681 - fewer hotspot object allocations (srawlins)
11
+ * #815 - autoload parsers for load-time speed and memory usage (grosser)
6
12
 
7
13
  Bugs:
14
+ * #736 - Mail.new copes with non-UTF8 messages marked as UTF8 (jeremy)
8
15
 
9
16
  == Version 2.6.1 - Sun Jun 8 15:34 +1100 2014 Mikel Lindsaar <mikel@reinteractive.net>
10
17
 
data/README.md CHANGED
@@ -15,7 +15,7 @@ Built from my experience with TMail, it is designed to be a pure ruby
15
15
  implementation that makes generating, sending and parsing emails a no
16
16
  brainer.
17
17
 
18
- It is also designed form the ground up to work with the more modern versions
18
+ It is also designed from the ground up to work with the more modern versions
19
19
  of Ruby. This is because Ruby > 1.9 handles text encodings much more wonderfully
20
20
  than Ruby 1.8.x and so these features have been taken full advantage of in this
21
21
  library allowing Mail to handle a lot more messages more cleanly than TMail.
@@ -44,10 +44,10 @@ Compatibility
44
44
 
45
45
  Every Mail commit is tested by Travis on the [following platforms](https://github.com/mikel/mail/blob/master/.travis.yml)
46
46
 
47
- * ruby-1.8.7-p374 [ i686 ]
48
- * ruby-1.9.2-p320 [ x86_64 ]
49
- * ruby-1.9.3-p327 [ x86_64 ]
50
- * ruby-2.0.0-p451 [ x86_64 ]
47
+ * ruby-1.8.7 [ i686 ]
48
+ * ruby-1.9.2 [ x86_64 ]
49
+ * ruby-1.9.3 [ x86_64 ]
50
+ * ruby-2.0.0 [ x86_64 ]
51
51
  * ruby-2.1.2 [ x86_64 ]
52
52
  * ruby-head [ x86_64 ]
53
53
  * jruby [ x86_64 ]
@@ -41,7 +41,7 @@ module Mail # :doc:
41
41
  require 'mail/multibyte'
42
42
  end
43
43
 
44
- require 'mail/patterns'
44
+ require 'mail/constants'
45
45
  require 'mail/utilities'
46
46
  require 'mail/configuration'
47
47
 
@@ -76,13 +76,14 @@ module Mail # :doc:
76
76
 
77
77
  require 'mail/envelope'
78
78
 
79
- require 'mail/parsers'
79
+ register_autoload :Parsers, "mail/parsers"
80
80
 
81
81
  # Autoload header field elements and transfer encodings.
82
82
  require 'mail/elements'
83
83
  require 'mail/encodings'
84
84
  require 'mail/encodings/base64'
85
85
  require 'mail/encodings/quoted_printable'
86
+ require 'mail/encodings/unix_to_unix'
86
87
 
87
88
  require 'mail/matchers/has_sent_mail'
88
89
 
@@ -254,18 +254,19 @@ module Mail
254
254
  @parts = Mail::PartsList.new[val]
255
255
  end
256
256
  end
257
-
257
+
258
258
  def split!(boundary)
259
259
  self.boundary = boundary
260
- parts = raw_source.split(/(?:\A|\r\n)--#{Regexp.escape(boundary || "")}(?=(?:--)?\s*$)/)
260
+ parts = extract_parts
261
+
261
262
  # Make the preamble equal to the preamble (if any)
262
263
  self.preamble = parts[0].to_s.strip
263
264
  # Make the epilogue equal to the epilogue (if any)
264
- self.epilogue = parts[-1].to_s.sub('--', '').strip
265
+ self.epilogue = parts[-1].to_s.strip
265
266
  parts[1...-1].to_a.each { |part| @parts << Mail::Part.new(part) }
266
267
  self
267
268
  end
268
-
269
+
269
270
  def only_us_ascii?
270
271
  !(raw_source =~ /[^\x01-\x7f]/)
271
272
  end
@@ -275,6 +276,29 @@ module Mail
275
276
  end
276
277
 
277
278
  private
279
+
280
+ # split parts by boundary, ignore first part if empty, append final part when closing boundary was missing
281
+ def extract_parts
282
+ parts_regex = /
283
+ (?: # non-capturing group
284
+ \A | # start of string OR
285
+ \r\n # line break
286
+ )
287
+ (
288
+ --#{Regexp.escape(boundary || "")} # boundary delimiter
289
+ (?:--)? # with non-capturing optional closing
290
+ )
291
+ (?=\s*$) # lookahead matching zero or more spaces followed by line-ending
292
+ /x
293
+ parts = raw_source.split(parts_regex).each_slice(2).to_a
294
+ parts.each_with_index { |(part, _), index| parts.delete_at(index) if index > 0 && part.blank? }
295
+
296
+ if parts.size > 1
297
+ final_separator = parts[-2][1]
298
+ parts << [""] if final_separator != "--#{boundary}--"
299
+ end
300
+ parts.map(&:first)
301
+ end
278
302
 
279
303
  def crlf_boundary
280
304
  "\r\n--#{boundary}\r\n"
@@ -1,20 +1,20 @@
1
1
  # encoding: us-ascii
2
2
  module Mail
3
- module Patterns
3
+ module Constants
4
4
  white_space = %Q|\x9\x20|
5
5
  text = %Q|\x1-\x8\xB\xC\xE-\x7f|
6
6
  field_name = %Q|\x21-\x39\x3b-\x7e|
7
7
  qp_safe = %Q|\x20-\x3c\x3e-\x7e|
8
-
8
+
9
9
  aspecial = %Q|()<>[]:;@\\,."| # RFC5322
10
10
  tspecial = %Q|()<>@,;:\\"/[]?=| # RFC2045
11
11
  sp = %Q| |
12
12
  control = %Q|\x00-\x1f\x7f-\xff|
13
-
13
+
14
14
  if control.respond_to?(:force_encoding)
15
15
  control = control.force_encoding(Encoding::BINARY)
16
16
  end
17
-
17
+
18
18
  CRLF = /\r\n/
19
19
  WSP = /[#{white_space}]/
20
20
  FWS = /#{CRLF}#{WSP}*/
@@ -33,5 +33,23 @@ module Mail
33
33
  ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{sp}]/n
34
34
  PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
35
35
  TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{sp}]/n
36
+ ENCODED_VALUE = /\=\?[^?]+\?([QB])\?[^?]*?\?\=/mi
37
+
38
+ EMPTY = ''
39
+ SPACE = ' '
40
+ UNDERSCORE = '_'
41
+ HYPHEN = '-'
42
+ COLON = ':'
43
+ ASTERISK = '*'
44
+ CR = "\r"
45
+ LF = "\n"
46
+ CR_ENCODED = "=0D"
47
+ LF_ENCODED = "=0A"
48
+ CAPITAL_M = 'M'
49
+ EQUAL_LF = "=\n"
50
+ NULL_SENDER = '<>'
51
+
52
+ Q_VALUES = ['Q','q']
53
+ B_VALUES = ['B','b']
36
54
  end
37
55
  end
@@ -1,6 +1,9 @@
1
1
  # encoding: utf-8
2
2
  class String #:nodoc:
3
3
 
4
+ CRLF = "\r\n"
5
+ LF = "\n"
6
+
4
7
  if RUBY_VERSION >= '1.9'
5
8
  # This 1.9 only regex can save a reasonable amount of time (~20%)
6
9
  # by not matching "\r\n" so the string is returned unchanged in
@@ -11,11 +14,11 @@ class String #:nodoc:
11
14
  end
12
15
 
13
16
  def to_crlf
14
- to_str.gsub(CRLF_REGEX, "\r\n")
17
+ to_str.gsub(CRLF_REGEX, CRLF)
15
18
  end
16
19
 
17
20
  def to_lf
18
- to_str.gsub(/\r\n|\r/, "\n")
21
+ to_str.gsub(/\r\n|\r/, LF)
19
22
  end
20
23
 
21
24
  unless String.instance_methods(false).map {|m| m.to_sym}.include?(:blank?)
@@ -3,15 +3,15 @@ module Mail
3
3
  class Address
4
4
 
5
5
  include Mail::Utilities
6
-
6
+
7
7
  # Mail::Address handles all email addresses in Mail. It takes an email address string
8
8
  # and parses it, breaking it down into its component parts and allowing you to get the
9
9
  # address, comments, display name, name, local part, domain part and fully formatted
10
10
  # address.
11
- #
11
+ #
12
12
  # Mail::Address requires a correctly formatted email address per RFC2822 or RFC822. It
13
13
  # handles all obsolete versions including obsolete domain routing on the local part.
14
- #
14
+ #
15
15
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
16
16
  # a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
17
17
  # a.address #=> 'mikel@test.lindsaar.net'
@@ -25,12 +25,11 @@ module Mail
25
25
  if value.nil?
26
26
  @parsed = false
27
27
  @data = nil
28
- return
29
28
  else
30
29
  parse(value)
31
30
  end
32
31
  end
33
-
32
+
34
33
  # Returns the raw input of the passed in string, this is before it is passed
35
34
  # by the parser.
36
35
  def raw
@@ -47,37 +46,37 @@ module Mail
47
46
  def format
48
47
  parse unless @parsed
49
48
  if @data.nil?
50
- ''
49
+ EMPTY
51
50
  elsif display_name
52
- [quote_phrase(display_name), "<#{address}>", format_comments].compact.join(" ")
51
+ [quote_phrase(display_name), "<#{address}>", format_comments].compact.join(SPACE)
53
52
  elsif address
54
- [address, format_comments].compact.join(" ")
53
+ [address, format_comments].compact.join(SPACE)
55
54
  else
56
55
  raw
57
56
  end
58
57
  end
59
58
 
60
- # Returns the address that is in the address itself. That is, the
59
+ # Returns the address that is in the address itself. That is, the
61
60
  # local@domain string, without any angle brackets or the like.
62
- #
61
+ #
63
62
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
64
63
  # a.address #=> 'mikel@test.lindsaar.net'
65
64
  def address
66
65
  parse unless @parsed
67
66
  domain ? "#{local}@#{domain}" : local
68
67
  end
69
-
68
+
70
69
  # Provides a way to assign an address to an already made Mail::Address object.
71
- #
70
+ #
72
71
  # a = Address.new
73
72
  # a.address = 'Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>'
74
73
  # a.address #=> 'mikel@test.lindsaar.net'
75
74
  def address=(value)
76
75
  parse(value)
77
76
  end
78
-
77
+
79
78
  # Returns the display name of the email address passed in.
80
- #
79
+ #
81
80
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
82
81
  # a.display_name #=> 'Mikel Lindsaar'
83
82
  def display_name
@@ -85,9 +84,9 @@ module Mail
85
84
  @display_name ||= get_display_name
86
85
  Encodings.decode_encode(@display_name.to_s, @output_type) if @display_name
87
86
  end
88
-
87
+
89
88
  # Provides a way to assign a display name to an already made Mail::Address object.
90
- #
89
+ #
91
90
  # a = Address.new
92
91
  # a.address = 'mikel@test.lindsaar.net'
93
92
  # a.display_name = 'Mikel Lindsaar'
@@ -98,7 +97,7 @@ module Mail
98
97
 
99
98
  # Returns the local part (the left hand side of the @ sign in the email address) of
100
99
  # the address
101
- #
100
+ #
102
101
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
103
102
  # a.local #=> 'mikel'
104
103
  def local
@@ -108,47 +107,43 @@ module Mail
108
107
 
109
108
  # Returns the domain part (the right hand side of the @ sign in the email address) of
110
109
  # the address
111
- #
110
+ #
112
111
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
113
112
  # a.domain #=> 'test.lindsaar.net'
114
113
  def domain
115
114
  parse unless @parsed
116
115
  strip_all_comments(get_domain) if get_domain
117
116
  end
118
-
117
+
119
118
  # Returns an array of comments that are in the email, or an empty array if there
120
119
  # are no comments
121
- #
120
+ #
122
121
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
123
122
  # a.comments #=> ['My email address']
124
123
  def comments
125
124
  parse unless @parsed
126
- if get_comments.empty?
127
- nil
128
- else
129
- get_comments.map { |c| c.squeeze(" ") }
130
- end
125
+ get_comments.map { |c| c.squeeze(SPACE) } unless get_comments.empty?
131
126
  end
132
-
127
+
133
128
  # Sometimes an address will not have a display name, but might have the name
134
129
  # as a comment field after the address. This returns that name if it exists.
135
- #
130
+ #
136
131
  # a = Address.new('mikel@test.lindsaar.net (Mikel Lindsaar)')
137
132
  # a.name #=> 'Mikel Lindsaar'
138
133
  def name
139
134
  parse unless @parsed
140
135
  get_name
141
136
  end
142
-
137
+
143
138
  # Returns the format of the address, or returns nothing
144
- #
139
+ #
145
140
  # a = Address.new('Mikel Lindsaar (My email address) <mikel@test.lindsaar.net>')
146
141
  # a.format #=> 'Mikel Lindsaar <mikel@test.lindsaar.net> (My email address)'
147
142
  def to_s
148
143
  parse unless @parsed
149
144
  format
150
145
  end
151
-
146
+
152
147
  # Shows the Address object basic details, including the Address
153
148
  # a = Address.new('Mikel (My email) <mikel@test.lindsaar.net>')
154
149
  # a.inspect #=> "#<Mail::Address:14184910 Address: |Mikel <mikel@test.lindsaar.net> (My email)| >"
@@ -156,43 +151,42 @@ module Mail
156
151
  parse unless @parsed
157
152
  "#<#{self.class}:#{self.object_id} Address: |#{to_s}| >"
158
153
  end
159
-
154
+
160
155
  def encoded
161
156
  @output_type = :encode
162
157
  format
163
158
  end
164
-
159
+
165
160
  def decoded
166
161
  @output_type = :decode
167
162
  format
168
163
  end
169
164
 
165
+ def group
166
+ @data && @data.group
167
+ end
168
+
170
169
  private
171
-
170
+
172
171
  def parse(value = nil)
173
172
  @parsed = true
173
+ @data = nil
174
174
 
175
175
  case value
176
- when NilClass
177
- @data = nil
178
- nil
179
176
  when Mail::Parsers::AddressStruct
180
177
  @data = value
181
178
  when String
182
- @raw_text = value
183
- if value.blank?
184
- @data = nil
185
- else
179
+ unless value.blank?
186
180
  address_list = Mail::Parsers::AddressListsParser.new.parse(value)
187
181
  @data = address_list.addresses.first
188
182
  end
189
183
  end
190
184
  end
191
-
185
+
192
186
  def strip_all_comments(string)
193
187
  unless comments.blank?
194
188
  comments.each do |comment|
195
- string = string.gsub("(#{comment})", '')
189
+ string = string.gsub("(#{comment})", EMPTY)
196
190
  end
197
191
  end
198
192
  string.strip
@@ -202,53 +196,36 @@ module Mail
202
196
  unless comments.blank?
203
197
  comments.each do |comment|
204
198
  if @data.domain && @data.domain.include?("(#{comment})")
205
- value = value.gsub("(#{comment})", '')
199
+ value = value.gsub("(#{comment})", EMPTY)
206
200
  end
207
201
  end
208
202
  end
209
203
  value.to_s.strip
210
204
  end
211
-
205
+
212
206
  def get_display_name
213
207
  if @data.display_name
214
208
  str = strip_all_comments(@data.display_name.to_s)
215
- elsif @data.comments
216
- if @data.domain
217
- str = strip_domain_comments(format_comments)
218
- else
219
- str = nil
220
- end
221
- else
222
- nil
223
- end
224
-
225
- if str.blank?
226
- nil
227
- else
228
- str
209
+ elsif @data.comments && @data.domain
210
+ str = strip_domain_comments(format_comments)
229
211
  end
212
+
213
+ str unless str.blank?
230
214
  end
231
-
215
+
232
216
  def get_name
233
217
  if display_name
234
218
  str = display_name
235
- else
236
- if comments
237
- comment_text = comments.join(' ').squeeze(" ")
238
- str = "(#{comment_text})"
239
- end
219
+ elsif comments
220
+ str = "(#{comments.join(SPACE).squeeze(SPACE)})"
240
221
  end
241
222
 
242
- if str.blank?
243
- nil
244
- else
245
- unparen(str)
246
- end
223
+ unparen(str) unless str.blank?
247
224
  end
248
-
225
+
249
226
  def format_comments
250
227
  if comments
251
- comment_text = comments.map {|c| escape_paren(c) }.join(' ').squeeze(" ")
228
+ comment_text = comments.map {|c| escape_paren(c) }.join(SPACE).squeeze(SPACE)
252
229
  @format_comments ||= "(#{comment_text})"
253
230
  else
254
231
  nil
@@ -262,7 +239,7 @@ module Mail
262
239
  def get_domain
263
240
  @data && @data.domain
264
241
  end
265
-
242
+
266
243
  def get_comments
267
244
  @data && @data.comments
268
245
  end