mail 2.6.1 → 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.
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