mail 1.1.0 → 1.2.1
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 +14 -1
- data/README.rdoc +37 -13
- data/lib/mail/configuration.rb +4 -0
- data/lib/mail/elements/address.rb +11 -0
- data/lib/mail/encodings/base64.rb +1 -0
- data/lib/mail/encodings/encodings.rb +133 -6
- data/lib/mail/encodings/quoted_printable.rb +1 -1
- data/lib/mail/field.rb +34 -34
- data/lib/mail/fields/bcc_field.rb +10 -1
- data/lib/mail/fields/cc_field.rb +10 -1
- data/lib/mail/fields/comments_field.rb +2 -1
- data/lib/mail/fields/common/common_address.rb +25 -1
- data/lib/mail/fields/common/common_date.rb +8 -5
- data/lib/mail/fields/common/common_field.rb +2 -74
- data/lib/mail/fields/common/common_message_id.rb +10 -0
- data/lib/mail/fields/common/parameter_hash.rb +10 -0
- data/lib/mail/fields/content_description_field.rb +3 -2
- data/lib/mail/fields/content_disposition_field.rb +12 -2
- data/lib/mail/fields/content_id_field.rb +12 -2
- data/lib/mail/fields/content_location_field.rb +12 -2
- data/lib/mail/fields/content_transfer_encoding_field.rb +12 -2
- data/lib/mail/fields/content_type_field.rb +26 -5
- data/lib/mail/fields/date_field.rb +11 -2
- data/lib/mail/fields/from_field.rb +10 -1
- data/lib/mail/fields/in_reply_to_field.rb +10 -1
- data/lib/mail/fields/keywords_field.rb +10 -1
- data/lib/mail/fields/message_id_field.rb +11 -2
- data/lib/mail/fields/mime_version_field.rb +11 -2
- data/lib/mail/fields/received_field.rb +10 -1
- data/lib/mail/fields/references_field.rb +10 -1
- data/lib/mail/fields/reply_to_field.rb +10 -1
- data/lib/mail/fields/resent_bcc_field.rb +10 -1
- data/lib/mail/fields/resent_cc_field.rb +10 -1
- data/lib/mail/fields/resent_date_field.rb +10 -1
- data/lib/mail/fields/resent_from_field.rb +10 -1
- data/lib/mail/fields/resent_message_id_field.rb +10 -1
- data/lib/mail/fields/resent_sender_field.rb +10 -1
- data/lib/mail/fields/resent_to_field.rb +10 -1
- data/lib/mail/fields/return_path_field.rb +10 -1
- data/lib/mail/fields/sender_field.rb +10 -1
- data/lib/mail/fields/subject_field.rb +2 -1
- data/lib/mail/fields/to_field.rb +10 -1
- data/lib/mail/fields/unstructured_field.rb +82 -0
- data/lib/mail/header.rb +2 -2
- data/lib/mail/message.rb +25 -9
- data/lib/mail/utilities.rb +7 -1
- data/lib/mail/version.rb +2 -2
- data/lib/mail/version_specific/ruby_1_8.rb +30 -10
- data/lib/mail/version_specific/ruby_1_9.rb +30 -8
- metadata +1 -1
data/CHANGELOG.rdoc
CHANGED
@@ -1,6 +1,19 @@
|
|
1
|
+
== Wed Nov 4 23:24:32 UTC 2009 Mikel Lindsaar <raasdnil@gmail.com>
|
2
|
+
|
3
|
+
* Created commit 2b5d608: Closes Issue #2 - Empty header field values not parsing <mikel>
|
4
|
+
* Version bumb to 1.2.1
|
5
|
+
|
6
|
+
== Wed Nov 4 12:54:43 UTC 2009 Mikel Lindsaar <raasdnil@gmail.com>
|
7
|
+
|
8
|
+
* Renamed Mail::Message.encode! to Mail::Message.ready_to_send!, deprecated :encode! <mikel>
|
9
|
+
* Rewrote encoding and decoding methods on all classes. Adds a lot of boiler plate code, but allows us to
|
10
|
+
be really precise in each field type that needs custom encoding. Now all encoding is done by the field_type
|
11
|
+
itself. Need to follow through on the body. <mikel>
|
12
|
+
* Bump version to 1.2.0 due to changes of :encoded, :decoded behaviour <mikel>
|
13
|
+
|
1
14
|
== Tue Nov 3 00:59:45 UTC 2009 Mikel Lindsaar <raasdnil@gmail.com>
|
2
15
|
|
3
|
-
* Tested mail against entire Enron set (2.3gb) and the Trec 2005 set (0.5gb), ~ half a million emails without crashing
|
16
|
+
* Tested mail against entire Enron set (2.3gb) and the Trec 2005 set (0.5gb), ~ half a million emails without crashing <jlindley>
|
4
17
|
* Some headers only can appear once, enforce during header parse assignment. <jlindley>
|
5
18
|
* Convert empty bodies into empty arrays instead of nil. <jlindley>
|
6
19
|
* Handle blank content dispositions. <jlindley>
|
data/README.rdoc
CHANGED
@@ -49,7 +49,8 @@ and you can manually create any other type of MIME email.
|
|
49
49
|
|
50
50
|
Next TODO:
|
51
51
|
|
52
|
-
*
|
52
|
+
* Improve MIME support for character sets in headers, currently works, mostly, needs
|
53
|
+
refinement.
|
53
54
|
* Add IMAP wrapper
|
54
55
|
|
55
56
|
== Testing Policy
|
@@ -64,13 +65,10 @@ It also means you can be sure Mail will behave correctly.
|
|
64
65
|
|
65
66
|
== API Policy
|
66
67
|
|
67
|
-
|
68
|
+
No API removals within a single point release. All removals to be depreciated with
|
69
|
+
warnings for at least one MINOR point release before removal.
|
68
70
|
|
69
|
-
|
70
|
-
once 1.0.0 is released, no API removals within a single point release. All removals
|
71
|
-
to be depreciated with warnings for at least one point release before removal.
|
72
|
-
|
73
|
-
Also, all private or protected methods to be declared as such.
|
71
|
+
Also, all private or protected methods to be declared as such - though this is still I/P.
|
74
72
|
|
75
73
|
== Installation
|
76
74
|
|
@@ -89,7 +87,34 @@ may or may not be a problem for you.
|
|
89
87
|
|
90
88
|
If you want to install mail manually, you can download the gem from github and do:
|
91
89
|
|
92
|
-
# gem install mail-1.
|
90
|
+
# gem install mail-1.2.1.gem
|
91
|
+
|
92
|
+
== Encodings
|
93
|
+
|
94
|
+
If you didn't know, handling encodings in Emails is not as straight forward as you
|
95
|
+
would hope.
|
96
|
+
|
97
|
+
I have tried to simplify it some:
|
98
|
+
|
99
|
+
1. All objects that can render into an email, have an :encoded method. Encoded will
|
100
|
+
return the object as a complete string ready to send in the mail system, that is,
|
101
|
+
it will include the header field and value and CRLF at the end and wrapped as
|
102
|
+
needed.
|
103
|
+
|
104
|
+
2. All objects that can render into an email, have a :decoded method. Decoded will
|
105
|
+
return the object's "value" only as a string. This means it will not include
|
106
|
+
the header fields (like 'To:' or 'Subject:').
|
107
|
+
|
108
|
+
3. By default, calling :to_s on an object will call it's encoded method, that is, make
|
109
|
+
it ready to send in an email.
|
110
|
+
|
111
|
+
4. Structured fields that have parameter values that can be encoded (e.g. Content-Type) will
|
112
|
+
provide decoded parameter values when you call the parameter names as methods against
|
113
|
+
the object.
|
114
|
+
|
115
|
+
5. Structured fields that have parameter values that can be encoded (e.g. Content-Type) will
|
116
|
+
provide encoded parameter values when you call the parameter names through the
|
117
|
+
object.parameters['<parameter_name>'] method call.
|
93
118
|
|
94
119
|
== Contributing
|
95
120
|
|
@@ -417,11 +442,10 @@ Of course... Mail will round trip an attachment as well
|
|
417
442
|
|
418
443
|
== Excerpts from TREC Spam Corpus 2005
|
419
444
|
|
420
|
-
The spec fixture files in spec/fixtures/emails/from_trec_2005
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
email parser implementation.
|
445
|
+
The spec fixture files in spec/fixtures/emails/from_trec_2005 are from the
|
446
|
+
2005 TREC Public Spam Corpus. They remain copyrighted under the terms of
|
447
|
+
that project and license agreement. They are used in this project to verify
|
448
|
+
and describe the development of this email parser implementation.
|
425
449
|
|
426
450
|
http://plg.uwaterloo.ca/~gvcormac/treccorpus/
|
427
451
|
|
data/lib/mail/configuration.rb
CHANGED
@@ -82,6 +82,7 @@ module Mail
|
|
82
82
|
def display_name
|
83
83
|
parse unless @parsed
|
84
84
|
@display_name ||= get_display_name
|
85
|
+
Encodings.decode_encode(@display_name, @output_type) if @display_name
|
85
86
|
end
|
86
87
|
|
87
88
|
# Provides a way to assign a display name to an already made Mail::Address object.
|
@@ -154,6 +155,16 @@ module Mail
|
|
154
155
|
parse unless @parsed
|
155
156
|
"#<#{self.class}:#{self.object_id} Address: |#{to_s}| >"
|
156
157
|
end
|
158
|
+
|
159
|
+
def encoded
|
160
|
+
@output_type = :encode
|
161
|
+
format
|
162
|
+
end
|
163
|
+
|
164
|
+
def decoded
|
165
|
+
@output_type = :decode
|
166
|
+
format
|
167
|
+
end
|
157
168
|
|
158
169
|
private
|
159
170
|
|
@@ -2,26 +2,153 @@
|
|
2
2
|
module Mail
|
3
3
|
module Encodings
|
4
4
|
|
5
|
+
include Mail::Patterns
|
6
|
+
|
7
|
+
# Is the encoding we want defined?
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# Encodings.defined?(:base64) #=> true
|
5
12
|
def Encodings.defined?( str )
|
6
13
|
string = str.to_s.split(/[_-]/).map { |v| v.capitalize }.join('')
|
7
14
|
RubyVer.has_constant?(Mail::Encodings, string)
|
8
15
|
end
|
9
16
|
|
17
|
+
# Gets a defined encoding type, QuotedPrintable or Base64 for now.
|
18
|
+
#
|
19
|
+
# Each encoding needs to be defined as a Mail::Encodings::ClassName for
|
20
|
+
# this to work, allows us to add other encodings in the future.
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
#
|
24
|
+
# Encodings.get_encoding(:base64) #=> Mail::Encodings::Base64
|
10
25
|
def Encodings.get_encoding( str )
|
11
26
|
string = str.to_s.split(/[_-]/).map { |v| v.capitalize }.join('')
|
12
27
|
RubyVer.get_constant(Mail::Encodings, string)
|
13
28
|
end
|
29
|
+
|
30
|
+
# Encodes a parameter value using URI Escaping, note the language field 'en' can
|
31
|
+
# be set using Mail::Configuration, like so:
|
32
|
+
#
|
33
|
+
# Mail.defaults.do
|
34
|
+
# param_encode_language 'jp'
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# The character set used for encoding will either be the value of $KCODE for
|
38
|
+
# Ruby < 1.9 or the encoding on the string passed in.
|
39
|
+
#
|
40
|
+
# Example:
|
41
|
+
#
|
42
|
+
# Mail::Encodings.param_encode("This is fun") #=> "us-ascii'en'This%20is%20fun"
|
43
|
+
def Encodings.param_encode(str)
|
44
|
+
RubyVer.param_encode(str)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Decodes a parameter value using URI Escaping.
|
48
|
+
#
|
49
|
+
# Example:
|
50
|
+
#
|
51
|
+
# Mail::Encodings.param_decode("This%20is%20fun", 'us-ascii') #=> "This is fun"
|
52
|
+
#
|
53
|
+
# str = Mail::Encodings.param_decode("This%20is%20fun", 'iso-8559-1')
|
54
|
+
# str.encoding #=> 'ISO-8859-1' ## Only on Ruby 1.9
|
55
|
+
# str #=> "This is fun"
|
56
|
+
def Encodings.param_decode(str, encoding)
|
57
|
+
RubyVer.param_decode(str, encoding)
|
58
|
+
end
|
14
59
|
|
15
|
-
|
16
|
-
|
60
|
+
# Decodes or encodes a string as needed for either Base64 or QP encoding types in
|
61
|
+
# the =?<encoding>?[QB]?<string>?=" format.
|
62
|
+
#
|
63
|
+
# The output type needs to be :decode to decode the input string or :encode to
|
64
|
+
# encode the input string. The character set used for encoding will either be
|
65
|
+
# the value of $KCODE for Ruby < 1.9 or the encoding on the string passed in.
|
66
|
+
#
|
67
|
+
# On encoding, will only send out Base64 encoded strings.
|
68
|
+
def Encodings.decode_encode(str, output_type)
|
69
|
+
case
|
70
|
+
when output_type == :decode
|
71
|
+
Encodings.value_decode(str)
|
72
|
+
else
|
73
|
+
if str.ascii_only?
|
74
|
+
str
|
75
|
+
else
|
76
|
+
Encodings.b_value_encode(str, find_encoding(str))
|
77
|
+
end
|
78
|
+
end
|
17
79
|
end
|
18
80
|
|
19
|
-
|
20
|
-
|
81
|
+
# Decodes a given string as Base64 or Quoted Printable, depending on what
|
82
|
+
# type it is.
|
83
|
+
#
|
84
|
+
#
|
85
|
+
def Encodings.value_decode(str)
|
86
|
+
case
|
87
|
+
when str =~ /\=\?.+?\?B\?.+?\?\=/
|
88
|
+
Encodings.b_value_decode(str)
|
89
|
+
when str =~ /\=\?.+?\?Q\?.+?\?\=/
|
90
|
+
Encodings.q_value_decode(str)
|
91
|
+
else
|
92
|
+
str
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Encode a string with Base64 Encoding and returns it ready to be inserted
|
97
|
+
# as a value for a field, that is, in the =?<charset>?B?<string>?= format
|
98
|
+
#
|
99
|
+
# Example:
|
100
|
+
#
|
101
|
+
# Encodings.b_value_encode('This is あ string', 'UTF-8')
|
102
|
+
# #=> "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?="
|
103
|
+
def Encodings.b_value_encode(str, encoding = nil)
|
104
|
+
string, encoding = RubyVer.b_value_encode(str, encoding)
|
105
|
+
string.split("\n").map do |str|
|
106
|
+
"=?#{encoding}?B?#{str.chomp}?="
|
107
|
+
end.join(" ")
|
21
108
|
end
|
22
109
|
|
23
|
-
|
24
|
-
|
110
|
+
# Decodes a Base64 string from the "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=" format
|
111
|
+
#
|
112
|
+
# Example:
|
113
|
+
#
|
114
|
+
# Encodings.b_value_encode("=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=")
|
115
|
+
# #=> 'This is あ string'
|
116
|
+
def Encodings.b_value_decode(str)
|
117
|
+
string = str.split(Mail::Patterns::WSP)
|
118
|
+
string.flatten.map do |s|
|
119
|
+
RubyVer.b_value_decode(s)
|
120
|
+
end.join('')
|
121
|
+
end
|
122
|
+
|
123
|
+
# Encode a string with Quoted-Printable Encoding and returns it ready to be inserted
|
124
|
+
# as a value for a field, that is, in the =?<charset>?Q?<string>?= format
|
125
|
+
#
|
126
|
+
# Example:
|
127
|
+
#
|
128
|
+
# Encodings.q_value_encode('This is あ string', 'UTF-8')
|
129
|
+
# #=> "=?UTF-8?Q?This_is_=E3=81=82_string?="
|
130
|
+
def Encodings.q_value_encode(str, encoding = nil)
|
131
|
+
string, encoding = RubyVer.q_value_encode(str, encoding)
|
132
|
+
"=?#{encoding}?Q?#{string.chomp}?="
|
133
|
+
end
|
134
|
+
|
135
|
+
# Decodes a Quoted-Printable string from the "=?UTF-8?Q?This_is_=E3=81=82_string?=" format
|
136
|
+
#
|
137
|
+
# Example:
|
138
|
+
#
|
139
|
+
# Encodings.b_value_encode("=?UTF-8?Q?This_is_=E3=81=82_string?=")
|
140
|
+
# #=> 'This is あ string'
|
141
|
+
def Encodings.q_value_decode(str)
|
142
|
+
string = str.split(Mail::Patterns::WSP)
|
143
|
+
string.flatten.map do |s|
|
144
|
+
RubyVer.q_value_decode(s)
|
145
|
+
end.join('')
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def Encodings.find_encoding(str)
|
151
|
+
RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
|
25
152
|
end
|
26
153
|
|
27
154
|
end
|
data/lib/mail/field.rb
CHANGED
@@ -69,7 +69,7 @@ module Mail
|
|
69
69
|
case
|
70
70
|
when name =~ /:/ && value.blank? # Field.new("field-name: field data")
|
71
71
|
name, value = split(name)
|
72
|
-
create_field(name, value)
|
72
|
+
create_field(name, value.to_s)
|
73
73
|
when name !~ /:/ && value.blank? # Field.new("field-name")
|
74
74
|
create_field(name, nil)
|
75
75
|
else # Field.new("field-name", "value")
|
@@ -111,8 +111,8 @@ module Mail
|
|
111
111
|
end
|
112
112
|
|
113
113
|
def <=>( other )
|
114
|
-
self_order = FIELD_ORDER.rindex(self.name.downcase) || 100
|
115
|
-
other_order = FIELD_ORDER.rindex(other.name.downcase) || 100
|
114
|
+
self_order = FIELD_ORDER.rindex(self.name.to_s.downcase) || 100
|
115
|
+
other_order = FIELD_ORDER.rindex(other.name.to_s.downcase) || 100
|
116
116
|
self_order <=> other_order
|
117
117
|
end
|
118
118
|
|
@@ -132,7 +132,7 @@ module Mail
|
|
132
132
|
private
|
133
133
|
|
134
134
|
def split(raw_field)
|
135
|
-
match_data = raw_field.match(/^(#{FIELD_NAME})\s*:\s*(#{FIELD_BODY})
|
135
|
+
match_data = raw_field.match(/^(#{FIELD_NAME})\s*:\s*(#{FIELD_BODY})?$/)
|
136
136
|
[match_data[1].to_s.strip, match_data[2].to_s.strip]
|
137
137
|
rescue
|
138
138
|
STDERR.puts "WARNING: Could not parse (and so ignorning) '#{raw_field}'"
|
@@ -149,64 +149,64 @@ module Mail
|
|
149
149
|
def new_field(name, value)
|
150
150
|
# Could do this with constantize and make it "as DRY as", but a simple case
|
151
151
|
# statement is, well, simpler...
|
152
|
-
case name
|
153
|
-
when /^to$/
|
152
|
+
case name
|
153
|
+
when /^to$/i
|
154
154
|
ToField.new(name, value)
|
155
|
-
when /^cc$/
|
155
|
+
when /^cc$/i
|
156
156
|
CcField.new(name, value)
|
157
|
-
when /^bcc$/
|
157
|
+
when /^bcc$/i
|
158
158
|
BccField.new(name, value)
|
159
|
-
when /^message-id$/
|
159
|
+
when /^message-id$/i
|
160
160
|
MessageIdField.new(name, value)
|
161
|
-
when /^in-reply-to$/
|
161
|
+
when /^in-reply-to$/i
|
162
162
|
InReplyToField.new(name, value)
|
163
|
-
when /^references$/
|
163
|
+
when /^references$/i
|
164
164
|
ReferencesField.new(name, value)
|
165
|
-
when /^subject$/
|
165
|
+
when /^subject$/i
|
166
166
|
SubjectField.new(name, value)
|
167
|
-
when /^comments$/
|
167
|
+
when /^comments$/i
|
168
168
|
CommentsField.new(name, value)
|
169
|
-
when /^keywords$/
|
169
|
+
when /^keywords$/i
|
170
170
|
KeywordsField.new(name, value)
|
171
|
-
when /^date$/
|
171
|
+
when /^date$/i
|
172
172
|
DateField.new(name, value)
|
173
|
-
when /^from$/
|
173
|
+
when /^from$/i
|
174
174
|
FromField.new(name, value)
|
175
|
-
when /^sender$/
|
175
|
+
when /^sender$/i
|
176
176
|
SenderField.new(name, value)
|
177
|
-
when /^reply-to$/
|
177
|
+
when /^reply-to$/i
|
178
178
|
ReplyToField.new(name, value)
|
179
|
-
when /^resent-date$/
|
179
|
+
when /^resent-date$/i
|
180
180
|
ResentDateField.new(name, value)
|
181
|
-
when /^resent-from$/
|
181
|
+
when /^resent-from$/i
|
182
182
|
ResentFromField.new(name, value)
|
183
|
-
when /^resent-sender$/
|
183
|
+
when /^resent-sender$/i
|
184
184
|
ResentSenderField.new(name, value)
|
185
|
-
when /^resent-to$/
|
185
|
+
when /^resent-to$/i
|
186
186
|
ResentToField.new(name, value)
|
187
|
-
when /^resent-cc$/
|
187
|
+
when /^resent-cc$/i
|
188
188
|
ResentCcField.new(name, value)
|
189
|
-
when /^resent-bcc$/
|
189
|
+
when /^resent-bcc$/i
|
190
190
|
ResentBccField.new(name, value)
|
191
|
-
when /^resent-message-id$/
|
191
|
+
when /^resent-message-id$/i
|
192
192
|
ResentMessageIdField.new(name, value)
|
193
|
-
when /^return-path$/
|
193
|
+
when /^return-path$/i
|
194
194
|
ReturnPathField.new(name, value)
|
195
|
-
when /^received$/
|
195
|
+
when /^received$/i
|
196
196
|
ReceivedField.new(name, value)
|
197
|
-
when /^mime-version$/
|
197
|
+
when /^mime-version$/i
|
198
198
|
MimeVersionField.new(name, value)
|
199
|
-
when /^content-transfer-encoding$/
|
199
|
+
when /^content-transfer-encoding$/i
|
200
200
|
ContentTransferEncodingField.new(name, value)
|
201
|
-
when /^content-description$/
|
201
|
+
when /^content-description$/i
|
202
202
|
ContentDescriptionField.new(name, value)
|
203
|
-
when /^content-disposition$/
|
203
|
+
when /^content-disposition$/i
|
204
204
|
ContentDispositionField.new(name, value)
|
205
|
-
when /^content-type$/
|
205
|
+
when /^content-type$/i
|
206
206
|
ContentTypeField.new(name, value)
|
207
|
-
when /^content-id$/
|
207
|
+
when /^content-id$/i
|
208
208
|
ContentIdField.new(name, value)
|
209
|
-
when /^content-location$/
|
209
|
+
when /^content-location$/i
|
210
210
|
ContentLocationField.new(name, value)
|
211
211
|
else
|
212
212
|
OptionalField.new(name, value)
|
@@ -31,9 +31,18 @@ module Mail
|
|
31
31
|
include Mail::CommonAddress
|
32
32
|
|
33
33
|
FIELD_NAME = 'bcc'
|
34
|
+
CAPITALIZED_FIELD = 'Bcc'
|
34
35
|
|
35
36
|
def initialize(*args)
|
36
|
-
super(
|
37
|
+
super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, args.last))
|
38
|
+
end
|
39
|
+
|
40
|
+
def encoded
|
41
|
+
do_encode(CAPITALIZED_FIELD)
|
42
|
+
end
|
43
|
+
|
44
|
+
def decoded
|
45
|
+
do_decode
|
37
46
|
end
|
38
47
|
|
39
48
|
end
|