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.
- data/CHANGELOG.rdoc +28 -0
- data/Manifest.txt +7 -6
- data/README.rdoc +19 -0
- data/Rakefile +3 -0
- data/lib/mail.rb +4 -3
- data/lib/mail/attachment.rb +9 -1
- data/lib/mail/body.rb +2 -2
- data/lib/mail/core_extensions/string.rb +0 -8
- data/lib/mail/elements/content_location_element.rb +25 -0
- data/lib/mail/encodings/encodings.rb +4 -0
- data/lib/mail/field.rb +32 -15
- data/lib/mail/fields/common/parameter_hash.rb +31 -0
- data/lib/mail/fields/content_disposition_field.rb +13 -1
- data/lib/mail/fields/content_location_field.rb +28 -0
- data/lib/mail/fields/content_type_field.rb +41 -5
- data/lib/mail/header.rb +25 -10
- data/lib/mail/message.rb +4 -0
- data/lib/mail/network/deliverable.rb +5 -1
- data/lib/mail/parsers/content_disposition.rb +14 -3
- data/lib/mail/parsers/content_disposition.treetop +3 -2
- data/lib/mail/parsers/content_location.rb +133 -0
- data/lib/mail/parsers/content_location.treetop +20 -0
- data/lib/mail/part.rb +19 -4
- data/lib/mail/version.rb +1 -1
- data/lib/mail/version_specific/ruby_1_8.rb +5 -2
- data/lib/mail/version_specific/ruby_1_9.rb +6 -0
- metadata +19 -7
- data/lib/mail/version_specific/multibyte.rb +0 -62
- data/lib/mail/version_specific/multibyte/chars.rb +0 -701
- data/lib/mail/version_specific/multibyte/exceptions.rb +0 -8
- data/lib/mail/version_specific/multibyte/unicode_database.rb +0 -71
- data/lib/mail/version_specific/ruby_1_8_string.rb +0 -88
data/CHANGELOG.rdoc
ADDED
@@ -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
|
data/Manifest.txt
CHANGED
@@ -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
|
data/README.rdoc
CHANGED
@@ -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
data/lib/mail.rb
CHANGED
@@ -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
|
data/lib/mail/attachment.rb
CHANGED
@@ -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
|
data/lib/mail/body.rb
CHANGED
@@ -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
|
|
@@ -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
|
data/lib/mail/field.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
56
|
-
|
57
|
-
|
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
|
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 =
|
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
|
-
|
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
|
37
|
-
|
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
|