mail 1.0.0 → 1.1.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.

@@ -0,0 +1,28 @@
1
+ == Tue Nov 3 00:59:45 UTC 2009 Mikel Lindsaar <raasdnil@gmail.com>
2
+
3
+ * Tested mail against entire Enron set (2.3gb) and the Trec 2005 set (0.5gb), ~ half a million emails without crashing
4
+ * Some headers only can appear once, enforce during header parse assignment. <jlindley>
5
+ * Convert empty bodies into empty arrays instead of nil. <jlindley>
6
+ * Handle blank content dispositions. <jlindley>
7
+ * Mention Trec 2005 Spam Corpus in readme <jlindley>
8
+ * Add 'rake corpus:verify_all' to allow parse checks in bulk. <jlindley>
9
+ * Added handling of multi value parameters, like filename*1*="us-ascii'en'blah" filename*2="bleh" <mikel>
10
+ * Added dependency on ActiveSupport 2.3 or higher <mikel>
11
+
12
+ == Sun Nov 1 12:00:00 UTC 2009 Mikel Lindsaar <raasdnil@gmail.com>
13
+
14
+ * handle OpenSSL::SSL::VERIFY_NONE returning 0 <jlindley>
15
+ * doing Mail.new { content_type [text, plain, { charset => UTF-8 }] } is now
16
+ possible (content type accepts an array) <mikel>
17
+
18
+ == Sat Oct 31 11:00:41 UTC 2009 Mikel Lindsaar <raasdnil@gmail.com>
19
+
20
+ * Fixed attachment handling, so mail can find attachment from a content-type,
21
+ content-disposition or content-location
22
+ * Added content-location field and parser
23
+ * Added message.has_attachments? for ActionMailer friendliness
24
+ * Added attachment.original_filename for ActionMailer friendliness
25
+
26
+ == Sat Oct 25 13:38:01 UTC 2009 Mikel Lindsaar <raasdnil@gmail.com>
27
+
28
+ * Birthday, Mail released as a gem... phew
@@ -1,7 +1,8 @@
1
1
  .gitignore
2
+ CHANGELOG.rdoc
2
3
  Manifest.txt
3
- README.rdoc
4
4
  Rakefile
5
+ README.rdoc
5
6
  lib/mail.rb
6
7
  lib/mail/attachment.rb
7
8
  lib/mail/body.rb
@@ -13,6 +14,7 @@ lib/mail/core_extensions.rb
13
14
  lib/mail/elements/address.rb
14
15
  lib/mail/elements/address_list.rb
15
16
  lib/mail/elements/content_disposition_element.rb
17
+ lib/mail/elements/content_location_element.rb
16
18
  lib/mail/elements/content_transfer_encoding_element.rb
17
19
  lib/mail/elements/content_type_element.rb
18
20
  lib/mail/elements/date_time_element.rb
@@ -34,9 +36,11 @@ lib/mail/fields/common/common_address.rb
34
36
  lib/mail/fields/common/common_date.rb
35
37
  lib/mail/fields/common/common_field.rb
36
38
  lib/mail/fields/common/common_message_id.rb
39
+ lib/mail/fields/common/parameter_hash.rb
37
40
  lib/mail/fields/content_description_field.rb
38
41
  lib/mail/fields/content_disposition_field.rb
39
42
  lib/mail/fields/content_id_field.rb
43
+ lib/mail/fields/content_location_field.rb
40
44
  lib/mail/fields/content_transfer_encoding_field.rb
41
45
  lib/mail/fields/content_type_field.rb
42
46
  lib/mail/fields/date_field.rb
@@ -71,6 +75,8 @@ lib/mail/parsers/address_lists.rb
71
75
  lib/mail/parsers/address_lists.treetop
72
76
  lib/mail/parsers/content_disposition.rb
73
77
  lib/mail/parsers/content_disposition.treetop
78
+ lib/mail/parsers/content_location.rb
79
+ lib/mail/parsers/content_location.treetop
74
80
  lib/mail/parsers/content_transfer_encoding.rb
75
81
  lib/mail/parsers/content_transfer_encoding.treetop
76
82
  lib/mail/parsers/content_type.rb
@@ -98,9 +104,4 @@ lib/mail/patterns.rb
98
104
  lib/mail/utilities.rb
99
105
  lib/mail/version.rb
100
106
  lib/mail/version_specific/ruby_1_8.rb
101
- lib/mail/version_specific/ruby_1_8_string.rb
102
107
  lib/mail/version_specific/ruby_1_9.rb
103
- lib/mail/version_specific/multibyte.rb
104
- lib/mail/version_specific/multibyte/chars.rb
105
- lib/mail/version_specific/multibyte/exceptions.rb
106
- lib/mail/version_specific/multibyte/unicode_database.rb
@@ -415,6 +415,25 @@ Of course... Mail will round trip an attachment as well
415
415
  @round_tripped_mail.attachments.length #=> 1
416
416
  @round_tripped_mail.attachments.first.filename #=> 'myfile.pdf'
417
417
 
418
+ == Excerpts from TREC Spam Corpus 2005
419
+
420
+ The spec fixture files in spec/fixtures/emails/from_trec_2005
421
+ are from the 2005 TREC Public Spam Corpus. They remain copyrighted
422
+ under the terms of that project and license agreement. They are used
423
+ in this project to verify and describe the development of this
424
+ email parser implementation.
425
+
426
+ http://plg.uwaterloo.ca/~gvcormac/treccorpus/
427
+
428
+ They are used as allowed by 'Permitted Uses, Clause 3':
429
+
430
+ "Small excerpts of the information may be displayed to others
431
+ or published in a scientific or technical context, solely for
432
+ the purpose of describing the research and development and
433
+ related issues."
434
+
435
+ -- http://plg.uwaterloo.ca/~gvcormac/treccorpus/
436
+
418
437
  == License:
419
438
 
420
439
  (The MIT License)
data/Rakefile CHANGED
@@ -36,3 +36,6 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
36
36
  rdoc.rdoc_files.include('README')
37
37
  rdoc.rdoc_files.include('lib/**/*.rb')
38
38
  end
39
+
40
+ # load custom rake tasks
41
+ Dir["#{File.dirname(__FILE__)}/lib/tasks/**/*.rake"].sort.each { |ext| load ext }
@@ -5,7 +5,9 @@ module Mail # :doc:
5
5
 
6
6
  gem "treetop", ">= 1.4"
7
7
  require 'treetop'
8
-
8
+ gem 'activesupport', ">= 2.3"
9
+ require 'activesupport'
10
+ require 'uri'
9
11
  require 'net/smtp'
10
12
  require 'mime/types'
11
13
  require 'tlsmail' if RUBY_VERSION <= '1.8.6'
@@ -17,7 +19,6 @@ module Mail # :doc:
17
19
  RubyVer = Mail::Ruby19
18
20
  else
19
21
  require File.join(dir_name, 'version_specific', 'ruby_1_8.rb')
20
- require File.join(dir_name, 'version_specific', 'ruby_1_8_string.rb')
21
22
  RubyVer = Mail::Ruby18
22
23
  end
23
24
 
@@ -50,7 +51,7 @@ module Mail # :doc:
50
51
  parsers = %w[ rfc2822_obsolete rfc2822 address_lists phrase_lists
51
52
  date_time received message_ids envelope_from rfc2045
52
53
  mime_version content_type content_disposition
53
- content_transfer_encoding ]
54
+ content_transfer_encoding content_location ]
54
55
 
55
56
  parsers.each do |parser|
56
57
  begin
@@ -49,7 +49,9 @@ module Mail
49
49
  def filename
50
50
  @filename
51
51
  end
52
-
52
+
53
+ alias :original_filename :filename
54
+
53
55
  def encoded
54
56
  @encoded_data
55
57
  end
@@ -70,6 +72,12 @@ module Mail
70
72
 
71
73
  def set_mime_type(filename, mime_type)
72
74
  unless mime_type
75
+ # Have to do this because MIME::Types is not Ruby 1.9 safe yet
76
+ if RUBY_VERSION >= '1.9'
77
+ new_file = String.new(filename).force_encoding(Encoding::BINARY)
78
+ ext = new_file.split('.'.force_encoding(Encoding::BINARY)).last
79
+ filename = "file.#{ext}".force_encoding('US-ASCII')
80
+ end
73
81
  @mime_type = MIME::Types.type_for(filename).first
74
82
  else
75
83
  @mime_type = mime_type
@@ -53,7 +53,7 @@ module Mail
53
53
  end
54
54
 
55
55
  alias :to_s :encoded
56
-
56
+
57
57
  def charset
58
58
  @charset
59
59
  end
@@ -124,7 +124,7 @@ module Mail
124
124
  self.preamble = parts[0].to_s.strip
125
125
  # Make the epilogue equal to the epilogue (if any)
126
126
  self.epilogue = parts[-1].to_s.sub('--', '').strip
127
- @parts = parts[1...-1].map { |part| Mail::Part.new(part) }
127
+ @parts = parts[1...-1].to_a.map { |part| Mail::Part.new(part) }
128
128
  self
129
129
  end
130
130
 
@@ -1,14 +1,6 @@
1
1
  # encoding: utf-8
2
2
  class String #:nodoc:
3
3
 
4
- if defined?(Mail::Multibyte)
5
- include Mail::Multibyte
6
- end
7
-
8
- def blank?
9
- self !~ /\S/
10
- end
11
-
12
4
  def to_crlf
13
5
  self.gsub(/\n|\r\n|\r/) { "\r\n" }
14
6
  end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+ class ContentLocationElement # :nodoc:
4
+
5
+ include Mail::Utilities
6
+
7
+ def initialize( string )
8
+ parser = Mail::ContentLocationParser.new
9
+ if tree = parser.parse(string)
10
+ @location = tree.location.text_value
11
+ else
12
+ raise Mail::Field::ParseError, "ContentLocationElement can not parse |#{string}|\nReason was: #{parser.failure_reason}\n"
13
+ end
14
+ end
15
+
16
+ def location
17
+ @location
18
+ end
19
+
20
+ def to_s(*args)
21
+ location.to_s
22
+ end
23
+
24
+ end
25
+ end
@@ -20,5 +20,9 @@ module Mail
20
20
  RubyVer.q_encode(str, encoding)
21
21
  end
22
22
 
23
+ def Encodings.param_decode(str, encoding)
24
+ RubyVer.param_decode(str, encoding)
25
+ end
26
+
23
27
  end
24
28
  end
@@ -24,8 +24,8 @@ module Mail
24
24
  include Comparable
25
25
 
26
26
  STRUCTURED_FIELDS = %w[ bcc cc content-description content-disposition
27
- content-id content-transfer-encoding content-type
28
- date from in-reply-to keywords message-id
27
+ content-id content-location content-transfer-encoding
28
+ content-type date from in-reply-to keywords message-id
29
29
  mime-version received references reply-to
30
30
  resent-bcc resent-cc resent-date resent-from
31
31
  resent-message-id resent-sender resent-to
@@ -46,18 +46,33 @@ module Mail
46
46
  class SyntaxError < FieldError #:nodoc:
47
47
  end
48
48
 
49
- # Accepts a text string in the format of:
49
+ # Accepts a string:
50
50
  #
51
- # "field-name: field data"
51
+ # Field.new("field-name: field data")
52
+ #
53
+ # Or name, value pair:
54
+ #
55
+ # Field.new("field-name", "value")
56
+ #
57
+ # Or a name by itself:
58
+ #
59
+ # Field.new("field-name")
52
60
  #
53
61
  # Note, does not want a terminating carriage return. Returns
54
- # self appropriately parsed
55
- def initialize(raw_field_text)
56
- if raw_field_text !~ /:/
57
- name = raw_field_text
62
+ # self appropriately parsed. If value is not a string, then
63
+ # it will be passed through as is, for example, content-type
64
+ # field can accept an array with the type and a hash of
65
+ # parameters:
66
+ #
67
+ # Field.new('content-type', ['text', 'plain', {:charset => 'UTF-8'}])
68
+ def initialize(name, value = nil)
69
+ case
70
+ when name =~ /:/ && value.blank? # Field.new("field-name: field data")
71
+ name, value = split(name)
72
+ create_field(name, value)
73
+ when name !~ /:/ && value.blank? # Field.new("field-name")
58
74
  create_field(name, nil)
59
- else
60
- name, value = split(raw_field_text)
75
+ else # Field.new("field-name", "value")
61
76
  create_field(name, value)
62
77
  end
63
78
  return self
@@ -112,7 +127,7 @@ module Mail
112
127
  message-id in-reply-to references
113
128
  subject comments keywords
114
129
  mime-version content-type content-transfer-encoding
115
- content-disposition content-description ]
130
+ content-location content-disposition content-description ]
116
131
 
117
132
  private
118
133
 
@@ -132,13 +147,13 @@ module Mail
132
147
  end
133
148
 
134
149
  def new_field(name, value)
135
- # Could do this with constantize and make it as DRY as, but a simple case is,
136
- # well, simpler...
150
+ # Could do this with constantize and make it "as DRY as", but a simple case
151
+ # statement is, well, simpler...
137
152
  case name.downcase
138
153
  when /^to$/
139
- ToField.new(name,value)
154
+ ToField.new(name, value)
140
155
  when /^cc$/
141
- CcField.new(name,value)
156
+ CcField.new(name, value)
142
157
  when /^bcc$/
143
158
  BccField.new(name, value)
144
159
  when /^message-id$/
@@ -191,6 +206,8 @@ module Mail
191
206
  ContentTypeField.new(name, value)
192
207
  when /^content-id$/
193
208
  ContentIdField.new(name, value)
209
+ when /^content-location$/
210
+ ContentLocationField.new(name, value)
194
211
  else
195
212
  OptionalField.new(name, value)
196
213
  end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ module Mail
3
+
4
+ # ParameterHash is an intelligent Hash that allows you to add
5
+ # parameter values including the mime extension paramaters that
6
+ # have the name*0="blah", name*1="bleh" keys, and will just return
7
+ # a single key called name="blahbleh" and do any required un-encoding
8
+ # to make that happen
9
+ class ParameterHash < Hash
10
+
11
+ include Enumerable
12
+
13
+ def [](key_name)
14
+ pairs = select { |k,v| k =~ /^#{key_name}\*/ }
15
+ pairs = pairs.to_a if RUBY_VERSION >= '1.9'
16
+ if pairs.empty? # Just dealing with a single value pair
17
+ super(key_name)
18
+ else # Dealing with a multiple value pair or a single encoded value pair
19
+ string = pairs.sort { |a,b| a.first <=> b.first }.map { |v| v.last }.join('')
20
+ if mt = string.match(/([\w\d\-]+)'(\w\w)'(.*)/)
21
+ string = mt[3]
22
+ encoding = mt[1]
23
+ else
24
+ encoding = nil
25
+ end
26
+ Mail::Encodings.param_decode(string, encoding)
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -25,10 +25,22 @@ module Mail
25
25
  end
26
26
 
27
27
  def parameters
28
- @parameters = Hash.new
28
+ @parameters = ParameterHash.new
29
29
  element.parameters.each { |p| @parameters.merge!(p) }
30
30
  @parameters
31
31
  end
32
32
 
33
+ def filename
34
+ case
35
+ when !parameters['filename'].blank?
36
+ @filename = parameters['filename']
37
+ when !parameters['name'].blank?
38
+ @filename = parameters['name']
39
+ else
40
+ @filename = nil
41
+ end
42
+ @filename
43
+ end
44
+
33
45
  end
34
46
  end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ #
3
+ #
4
+ #
5
+ module Mail
6
+ class ContentLocationField < StructuredField
7
+
8
+ FIELD_NAME = 'content-location'
9
+
10
+ def initialize(*args)
11
+ super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
12
+ end
13
+
14
+ def tree
15
+ @element ||= Mail::ContentLocationElement.new(value)
16
+ @tree ||= @element.tree
17
+ end
18
+
19
+ def element
20
+ @element ||= Mail::ContentLocationElement.new(value)
21
+ end
22
+
23
+ def location
24
+ element.location
25
+ end
26
+
27
+ end
28
+ end
@@ -8,7 +8,17 @@ module Mail
8
8
  FIELD_NAME = 'content-type'
9
9
 
10
10
  def initialize(*args)
11
- super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
11
+ if args.last.class == Array
12
+ @main_type = args.last[0]
13
+ @sub_type = args.last[1]
14
+ @parameters = args.last.last
15
+ super(FIELD_NAME, args.last)
16
+ else
17
+ @main_type = nil
18
+ @sub_type = nil
19
+ @parameters = nil
20
+ super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
21
+ end
12
22
  end
13
23
 
14
24
  def tree
@@ -21,11 +31,11 @@ module Mail
21
31
  end
22
32
 
23
33
  def main_type
24
- element.main_type
34
+ @main_type ||= element.main_type
25
35
  end
26
36
 
27
37
  def sub_type
28
- element.sub_type
38
+ @sub_type ||= element.sub_type
29
39
  end
30
40
 
31
41
  def content_type
@@ -33,8 +43,10 @@ module Mail
33
43
  end
34
44
 
35
45
  def parameters
36
- @parameters = Hash.new
37
- element.parameters.each { |p| @parameters.merge!(p) }
46
+ unless @parameters
47
+ @parameters = ParameterHash.new
48
+ element.parameters.each { |p| @parameters.merge!(p) }
49
+ end
38
50
  @parameters
39
51
  end
40
52
 
@@ -46,5 +58,29 @@ module Mail
46
58
  "--==_mimepart_#{Mail.random_tag}"
47
59
  end
48
60
 
61
+ def value
62
+ if @value.class == Array
63
+ "#{@main_type}/#{@sub_type}; #{stringify(parameters)}"
64
+ else
65
+ @value
66
+ end
67
+ end
68
+
69
+ def stringify(params)
70
+ params.map { |k,v| "#{k}=#{v}" }.join("; ")
71
+ end
72
+
73
+ def filename
74
+ case
75
+ when parameters['filename']
76
+ @filename = parameters['filename']
77
+ when parameters['name']
78
+ @filename = parameters['name']
79
+ else
80
+ @filename = nil
81
+ end
82
+ @filename
83
+ end
84
+
49
85
  end
50
86
  end