mail 1.0.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/.gitignore +4 -0
- data/Manifest.txt +106 -0
- data/README.rdoc +441 -0
- data/Rakefile +38 -0
- data/lib/mail.rb +86 -0
- data/lib/mail/attachment.rb +90 -0
- data/lib/mail/body.rb +149 -0
- data/lib/mail/configuration.rb +90 -0
- data/lib/mail/core_extensions.rb +6 -0
- data/lib/mail/core_extensions/blank.rb +41 -0
- data/lib/mail/core_extensions/nil.rb +15 -0
- data/lib/mail/core_extensions/string.rb +31 -0
- data/lib/mail/elements/address.rb +293 -0
- data/lib/mail/elements/address_list.rb +62 -0
- data/lib/mail/elements/content_disposition_element.rb +34 -0
- data/lib/mail/elements/content_transfer_encoding_element.rb +21 -0
- data/lib/mail/elements/content_type_element.rb +39 -0
- data/lib/mail/elements/date_time_element.rb +26 -0
- data/lib/mail/elements/envelope_from_element.rb +34 -0
- data/lib/mail/elements/message_ids_element.rb +29 -0
- data/lib/mail/elements/mime_version_element.rb +26 -0
- data/lib/mail/elements/phrase_list.rb +21 -0
- data/lib/mail/elements/received_element.rb +30 -0
- data/lib/mail/encodings/base64.rb +17 -0
- data/lib/mail/encodings/encodings.rb +24 -0
- data/lib/mail/encodings/quoted_printable.rb +26 -0
- data/lib/mail/envelope.rb +35 -0
- data/lib/mail/field.rb +202 -0
- data/lib/mail/field_list.rb +33 -0
- data/lib/mail/fields/bcc_field.rb +40 -0
- data/lib/mail/fields/cc_field.rb +40 -0
- data/lib/mail/fields/comments_field.rb +41 -0
- data/lib/mail/fields/common/common_address.rb +62 -0
- data/lib/mail/fields/common/common_date.rb +35 -0
- data/lib/mail/fields/common/common_field.rb +128 -0
- data/lib/mail/fields/common/common_message_id.rb +35 -0
- data/lib/mail/fields/content_description_field.rb +15 -0
- data/lib/mail/fields/content_disposition_field.rb +34 -0
- data/lib/mail/fields/content_id_field.rb +50 -0
- data/lib/mail/fields/content_transfer_encoding_field.rb +28 -0
- data/lib/mail/fields/content_type_field.rb +50 -0
- data/lib/mail/fields/date_field.rb +44 -0
- data/lib/mail/fields/from_field.rb +40 -0
- data/lib/mail/fields/in_reply_to_field.rb +42 -0
- data/lib/mail/fields/keywords_field.rb +22 -0
- data/lib/mail/fields/message_id_field.rb +70 -0
- data/lib/mail/fields/mime_version_field.rb +42 -0
- data/lib/mail/fields/optional_field.rb +11 -0
- data/lib/mail/fields/received_field.rb +49 -0
- data/lib/mail/fields/references_field.rb +42 -0
- data/lib/mail/fields/reply_to_field.rb +40 -0
- data/lib/mail/fields/resent_bcc_field.rb +40 -0
- data/lib/mail/fields/resent_cc_field.rb +40 -0
- data/lib/mail/fields/resent_date_field.rb +16 -0
- data/lib/mail/fields/resent_from_field.rb +40 -0
- data/lib/mail/fields/resent_message_id_field.rb +20 -0
- data/lib/mail/fields/resent_sender_field.rb +48 -0
- data/lib/mail/fields/resent_to_field.rb +40 -0
- data/lib/mail/fields/return_path_field.rb +34 -0
- data/lib/mail/fields/sender_field.rb +48 -0
- data/lib/mail/fields/structured_field.rb +32 -0
- data/lib/mail/fields/subject_field.rb +14 -0
- data/lib/mail/fields/to_field.rb +40 -0
- data/lib/mail/fields/unstructured_field.rb +27 -0
- data/lib/mail/header.rb +213 -0
- data/lib/mail/mail.rb +120 -0
- data/lib/mail/message.rb +648 -0
- data/lib/mail/network/deliverable.rb +42 -0
- data/lib/mail/network/retrievable.rb +63 -0
- data/lib/mail/parsers/address_lists.rb +61 -0
- data/lib/mail/parsers/address_lists.treetop +19 -0
- data/lib/mail/parsers/content_disposition.rb +358 -0
- data/lib/mail/parsers/content_disposition.treetop +45 -0
- data/lib/mail/parsers/content_transfer_encoding.rb +179 -0
- data/lib/mail/parsers/content_transfer_encoding.treetop +25 -0
- data/lib/mail/parsers/content_type.rb +507 -0
- data/lib/mail/parsers/content_type.treetop +58 -0
- data/lib/mail/parsers/date_time.rb +111 -0
- data/lib/mail/parsers/date_time.treetop +11 -0
- data/lib/mail/parsers/envelope_from.rb +188 -0
- data/lib/mail/parsers/envelope_from.treetop +32 -0
- data/lib/mail/parsers/message_ids.rb +42 -0
- data/lib/mail/parsers/message_ids.treetop +15 -0
- data/lib/mail/parsers/mime_version.rb +141 -0
- data/lib/mail/parsers/mime_version.treetop +19 -0
- data/lib/mail/parsers/phrase_lists.rb +42 -0
- data/lib/mail/parsers/phrase_lists.treetop +15 -0
- data/lib/mail/parsers/received.rb +68 -0
- data/lib/mail/parsers/received.treetop +11 -0
- data/lib/mail/parsers/rfc2045.rb +406 -0
- data/lib/mail/parsers/rfc2045.treetop +35 -0
- data/lib/mail/parsers/rfc2822.rb +5005 -0
- data/lib/mail/parsers/rfc2822.treetop +402 -0
- data/lib/mail/parsers/rfc2822_obsolete.rb +3607 -0
- data/lib/mail/parsers/rfc2822_obsolete.treetop +241 -0
- data/lib/mail/part.rb +120 -0
- data/lib/mail/patterns.rb +42 -0
- data/lib/mail/utilities.rb +142 -0
- data/lib/mail/version.rb +10 -0
- data/lib/mail/version_specific/multibyte.rb +62 -0
- data/lib/mail/version_specific/multibyte/chars.rb +701 -0
- data/lib/mail/version_specific/multibyte/exceptions.rb +8 -0
- data/lib/mail/version_specific/multibyte/unicode_database.rb +71 -0
- data/lib/mail/version_specific/ruby_1_8.rb +61 -0
- data/lib/mail/version_specific/ruby_1_8_string.rb +88 -0
- data/lib/mail/version_specific/ruby_1_9.rb +49 -0
- metadata +192 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# subject = "Subject:" unstructured CRLF
|
4
|
+
module Mail
|
5
|
+
class SubjectField < UnstructuredField
|
6
|
+
|
7
|
+
FIELD_NAME = 'subject'
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# = To Field
|
4
|
+
#
|
5
|
+
# The To field inherits to StructuredField and handles the To: header
|
6
|
+
# field in the email.
|
7
|
+
#
|
8
|
+
# Sending to to a mail message will instantiate a Mail::Field object that
|
9
|
+
# has a ToField as it's field type. This includes all Mail::CommonAddress
|
10
|
+
# module instance metods.
|
11
|
+
#
|
12
|
+
# Only one To field can appear in a header, though it can have multiple
|
13
|
+
# addresses and groups of addresses.
|
14
|
+
#
|
15
|
+
# == Examples:
|
16
|
+
#
|
17
|
+
# mail = Mail.new
|
18
|
+
# mail.to = 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
|
19
|
+
# mail.to #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
|
20
|
+
# mail[:to] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
|
21
|
+
# mail['to'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
|
22
|
+
# mail['To'] #=> '#<Mail::Field:0x180e5e8 @field=#<Mail::ToField:0x180e1c4
|
23
|
+
#
|
24
|
+
# mail.to.to_s #=> 'Mikel Lindsaar <mikel@test.lindsaar.net>, ada@test.lindsaar.net'
|
25
|
+
# mail.to.addresses #=> ['mikel@test.lindsaar.net', 'ada@test.lindsaar.net']
|
26
|
+
# mail.to.formatted #=> ['Mikel Lindsaar <mikel@test.lindsaar.net>', 'ada@test.lindsaar.net']
|
27
|
+
#
|
28
|
+
module Mail
|
29
|
+
class ToField < StructuredField
|
30
|
+
|
31
|
+
include Mail::CommonAddress
|
32
|
+
|
33
|
+
FIELD_NAME = 'to'
|
34
|
+
|
35
|
+
def initialize(*args)
|
36
|
+
super(FIELD_NAME, strip_field(FIELD_NAME, args.last))
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mail
|
3
|
+
# Provides access to an unstructured header field
|
4
|
+
#
|
5
|
+
# ===Per RFC 2822:
|
6
|
+
# 2.2.1. Unstructured Header Field Bodies
|
7
|
+
#
|
8
|
+
# Some field bodies in this standard are defined simply as
|
9
|
+
# "unstructured" (which is specified below as any US-ASCII characters,
|
10
|
+
# except for CR and LF) with no further restrictions. These are
|
11
|
+
# referred to as unstructured field bodies. Semantically, unstructured
|
12
|
+
# field bodies are simply to be treated as a single line of characters
|
13
|
+
# with no further processing (except for header "folding" and
|
14
|
+
# "unfolding" as described in section 2.2.3).
|
15
|
+
class UnstructuredField
|
16
|
+
|
17
|
+
include Mail::CommonField
|
18
|
+
include Mail::Utilities
|
19
|
+
|
20
|
+
def initialize(*args)
|
21
|
+
self.name = args.first
|
22
|
+
self.value = args.last
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/lib/mail/header.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mail
|
3
|
+
|
4
|
+
# Provides access to a header object.
|
5
|
+
#
|
6
|
+
# ===Per RFC2822
|
7
|
+
#
|
8
|
+
# 2.2. Header Fields
|
9
|
+
#
|
10
|
+
# Header fields are lines composed of a field name, followed by a colon
|
11
|
+
# (":"), followed by a field body, and terminated by CRLF. A field
|
12
|
+
# name MUST be composed of printable US-ASCII characters (i.e.,
|
13
|
+
# characters that have values between 33 and 126, inclusive), except
|
14
|
+
# colon. A field body may be composed of any US-ASCII characters,
|
15
|
+
# except for CR and LF. However, a field body may contain CRLF when
|
16
|
+
# used in header "folding" and "unfolding" as described in section
|
17
|
+
# 2.2.3. All field bodies MUST conform to the syntax described in
|
18
|
+
# sections 3 and 4 of this standard.
|
19
|
+
class Header
|
20
|
+
include Patterns
|
21
|
+
include Utilities
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
# Creates a new header object.
|
25
|
+
#
|
26
|
+
# Accepts raw text or nothing. If given raw text will attempt to parse
|
27
|
+
# it and split it into the various fields, instantiating each field as
|
28
|
+
# it goes.
|
29
|
+
#
|
30
|
+
# If it finds a field that should be a structured field (such as content
|
31
|
+
# type), but it fails to parse it, it will simply make it an unstructured
|
32
|
+
# field and leave it alone. This will mean that the data is preserved but
|
33
|
+
# no automatic processing of that field will happen. If you find one of
|
34
|
+
# these cases, please make a patch and send it in, or at the least, send
|
35
|
+
# me the example so we can fix it.
|
36
|
+
def initialize(header_text = nil)
|
37
|
+
self.raw_source = header_text.to_crlf
|
38
|
+
split_header if header_text
|
39
|
+
end
|
40
|
+
|
41
|
+
# The preserved raw source of the header as you passed it in, untouched
|
42
|
+
# for your Regexing glory.
|
43
|
+
def raw_source
|
44
|
+
@raw_source
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns an array of all the fields in the header in order that they
|
48
|
+
# were read in.
|
49
|
+
def fields
|
50
|
+
@fields ||= []
|
51
|
+
end
|
52
|
+
|
53
|
+
# 3.6. Field definitions
|
54
|
+
#
|
55
|
+
# It is important to note that the header fields are not guaranteed to
|
56
|
+
# be in a particular order. They may appear in any order, and they
|
57
|
+
# have been known to be reordered occasionally when transported over
|
58
|
+
# the Internet. However, for the purposes of this standard, header
|
59
|
+
# fields SHOULD NOT be reordered when a message is transported or
|
60
|
+
# transformed. More importantly, the trace header fields and resent
|
61
|
+
# header fields MUST NOT be reordered, and SHOULD be kept in blocks
|
62
|
+
# prepended to the message. See sections 3.6.6 and 3.6.7 for more
|
63
|
+
# information.
|
64
|
+
#
|
65
|
+
# Populates the fields container with Field objects in the order it
|
66
|
+
# receives them in.
|
67
|
+
#
|
68
|
+
# Acceps an array of field string values, for example:
|
69
|
+
#
|
70
|
+
# h = Header.new
|
71
|
+
# h.fields = ['From: mikel@me.com', 'To: bob@you.com']
|
72
|
+
def fields=(unfolded_fields)
|
73
|
+
@fields = Mail::FieldList.new
|
74
|
+
unfolded_fields.each do |field|
|
75
|
+
@fields << Field.new(field)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# 3.6. Field definitions
|
80
|
+
#
|
81
|
+
# The following table indicates limits on the number of times each
|
82
|
+
# field may occur in a message header as well as any special
|
83
|
+
# limitations on the use of those fields. An asterisk next to a value
|
84
|
+
# in the minimum or maximum column indicates that a special restriction
|
85
|
+
# appears in the Notes column.
|
86
|
+
#
|
87
|
+
# <snip table from 3.6>
|
88
|
+
#
|
89
|
+
# As per RFC, many fields can appear more than once, we will return a string
|
90
|
+
# of the value if there is only one header, or if there is more than one
|
91
|
+
# matching header, will return an array of values in order that they appear
|
92
|
+
# in the header ordered from top to bottom.
|
93
|
+
#
|
94
|
+
# Example:
|
95
|
+
#
|
96
|
+
# h = Header.new
|
97
|
+
# h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
|
98
|
+
# h['To'] #=> 'mikel@me.com'
|
99
|
+
# h['X-Mail-SPAM'] #=> ['15', '20']
|
100
|
+
def [](name)
|
101
|
+
selected = fields.select { |f| f.responsible_for?(name) }
|
102
|
+
case
|
103
|
+
when selected.length > 1
|
104
|
+
selected.map { |f| f }
|
105
|
+
when !selected.blank?
|
106
|
+
selected.first
|
107
|
+
else
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Sets the FIRST matching field in the header to passed value, or deletes
|
113
|
+
# the FIRST field matched from the header if passed nil
|
114
|
+
#
|
115
|
+
# Example:
|
116
|
+
#
|
117
|
+
# h = Header.new
|
118
|
+
# h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
|
119
|
+
# h['To'] = 'bob@you.com'
|
120
|
+
# h['To'] #=> 'bob@you.com'
|
121
|
+
# h['X-Mail-SPAM'] = '10000'
|
122
|
+
# h['X-Mail-SPAM'] # => ['15', '20', '10000']
|
123
|
+
# h['X-Mail-SPAM'] = nil
|
124
|
+
# h['X-Mail-SPAM'] # => nil
|
125
|
+
def []=(name, value)
|
126
|
+
selected = fields.select { |f| f.responsible_for?(name) }
|
127
|
+
case
|
128
|
+
# User wants to delete the field
|
129
|
+
when !selected.blank? && value == nil
|
130
|
+
fields.delete_if { |f| selected.include?(f) }
|
131
|
+
|
132
|
+
# User wants to change the field
|
133
|
+
when !selected.blank? && LIMITED_FIELDS.include?(name.downcase)
|
134
|
+
selected.first.update(name, value)
|
135
|
+
|
136
|
+
# User wants to create the field
|
137
|
+
else
|
138
|
+
# Need to insert in correct order for trace fields
|
139
|
+
if value.blank?
|
140
|
+
self.fields << Field.new(name)
|
141
|
+
else
|
142
|
+
self.fields << Field.new("#{name}: #{value}")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
LIMITED_FIELDS = %w[ orig-date from sender reply-to to cc bcc
|
148
|
+
message-id in-reply-to references subject
|
149
|
+
return-path content-type mime-version
|
150
|
+
content-transfer-encoding content-description
|
151
|
+
content-id content-type content-disposition]
|
152
|
+
|
153
|
+
def encoded
|
154
|
+
buffer = ''
|
155
|
+
fields.each do |field|
|
156
|
+
buffer << field.encoded.to_s
|
157
|
+
end
|
158
|
+
buffer
|
159
|
+
end
|
160
|
+
|
161
|
+
alias :to_s :encoded
|
162
|
+
|
163
|
+
# Returns true if the header has a Message-ID defined (empty or not)
|
164
|
+
def has_message_id?
|
165
|
+
!fields.select { |f| f.responsible_for?('Message-ID') }.empty?
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns true if the header has a Content-ID defined (empty or not)
|
169
|
+
def has_content_id?
|
170
|
+
!fields.select { |f| f.responsible_for?('Content-ID') }.empty?
|
171
|
+
end
|
172
|
+
|
173
|
+
# Returns true if the header has a Date defined (empty or not)
|
174
|
+
def has_date?
|
175
|
+
!fields.select { |f| f.responsible_for?('Date') }.empty?
|
176
|
+
end
|
177
|
+
|
178
|
+
# Returns true if the header has a message_id defined (empty or not)
|
179
|
+
def has_mime_version?
|
180
|
+
!fields.select { |f| f.responsible_for?('Mime-Version') }.empty?
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def raw_source=(val)
|
186
|
+
@raw_source = val
|
187
|
+
end
|
188
|
+
|
189
|
+
# 2.2.3. Long Header Fields
|
190
|
+
#
|
191
|
+
# The process of moving from this folded multiple-line representation
|
192
|
+
# of a header field to its single line representation is called
|
193
|
+
# "unfolding". Unfolding is accomplished by simply removing any CRLF
|
194
|
+
# that is immediately followed by WSP. Each header field should be
|
195
|
+
# treated in its unfolded form for further syntactic and semantic
|
196
|
+
# evaluation.
|
197
|
+
def unfold(string)
|
198
|
+
string.gsub(/#{CRLF}#{WSP}+/, ' ').gsub(/#{WSP}+/, ' ')
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns the header with all the folds removed
|
202
|
+
def unfolded_header
|
203
|
+
@unfolded_header ||= unfold(raw_source)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Splits an unfolded and line break cleaned header into individual field
|
207
|
+
# strings.
|
208
|
+
def split_header
|
209
|
+
self.fields = unfolded_header.split(CRLF)
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
data/lib/mail/mail.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mail
|
3
|
+
|
4
|
+
# Allows you to create a new Mail::Message object.
|
5
|
+
#
|
6
|
+
# You can make an email via passing a string or passing a block.
|
7
|
+
#
|
8
|
+
# For example, the following two examples will create the same email
|
9
|
+
# message:
|
10
|
+
#
|
11
|
+
# Creating via a string:
|
12
|
+
#
|
13
|
+
# string = 'To: mikel@test.lindsaar.net\r\n'
|
14
|
+
# string << 'From: bob@test.lindsaar.net\r\n\r\n'
|
15
|
+
# string << 'Subject: This is an email\r\n'
|
16
|
+
# string << '\r\n'
|
17
|
+
# string << 'This is the body'
|
18
|
+
# Mail.new(string)
|
19
|
+
#
|
20
|
+
# Or creating via a block:
|
21
|
+
#
|
22
|
+
# message = Mail.new do
|
23
|
+
# to 'mikel@test.lindsaar.net'
|
24
|
+
# from 'bob@test.lindsaar.net'
|
25
|
+
# subject 'This is an email'
|
26
|
+
# body 'This is the body'
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# As a side note, you can also create a new email through creating
|
30
|
+
# a Mail::Message object directly and then passing in values via string,
|
31
|
+
# symbol or direct method calls. See Mail::Message for more information.
|
32
|
+
#
|
33
|
+
# mail = Mail.new
|
34
|
+
# mail.to = 'mikel@test.lindsaar.net'
|
35
|
+
# mail[:from] = 'bob@test.lindsaar.net'
|
36
|
+
# mail['subject'] = 'This is an email'
|
37
|
+
# mail.body = 'This is the body'
|
38
|
+
def Mail.new(*args, &block)
|
39
|
+
if block_given?
|
40
|
+
Mail::Message.new(args, &block)
|
41
|
+
else
|
42
|
+
Mail::Message.new(args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Set the default configuration to send and receive emails. The defaults
|
47
|
+
# are global, allowing you to just call them once and use them everywhere.
|
48
|
+
# if port values are omitted from the SMTP and POP3 method calls, then it
|
49
|
+
# is assumed to use the default ports of 25 and 110 respectively.
|
50
|
+
#
|
51
|
+
# The methods you can call in the default configuration are:
|
52
|
+
#
|
53
|
+
# smtp(server_name, port):: Sets the SMTP server domain name and port
|
54
|
+
# pop3(server_name, port):: Sets the POP3 server domain name and port
|
55
|
+
# user(username):: Sets the username used for POP3 sessions
|
56
|
+
# pass(password):: Sets the password used for POP3 sessions
|
57
|
+
#
|
58
|
+
# Mail.defaults do
|
59
|
+
# smtp 'smtp.myhost.fr', 587
|
60
|
+
# pop3 'pop.myhost.fr'
|
61
|
+
# user 'bernardo'
|
62
|
+
# pass 'mypass'
|
63
|
+
# enable_tls
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# Once you have set defaults, you can call Mail.deliver to send an email
|
67
|
+
# through the Mail.deliver method.
|
68
|
+
def Mail.defaults(&block)
|
69
|
+
if block_given?
|
70
|
+
Mail::Configuration.instance.defaults(&block)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Send an email using the default configuration. You do need to set a default
|
75
|
+
# configuration first before you use Mail.deliver, if you don't, an appropriate
|
76
|
+
# error will be raised telling you to.
|
77
|
+
#
|
78
|
+
# Mail.deliver do
|
79
|
+
# to 'mikel@test.lindsaar.net'
|
80
|
+
# from 'ada@test.lindsaar.net'
|
81
|
+
# subject 'This is a test email'
|
82
|
+
# body 'Not much to say here'
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# And your email object will be created and sent.
|
86
|
+
|
87
|
+
def Mail.deliver(*args, &block)
|
88
|
+
Mail.new(args, &block).deliver!
|
89
|
+
end
|
90
|
+
|
91
|
+
def Mail.get_all_mail(&block)
|
92
|
+
Mail::Message.get_all_mail(&block)
|
93
|
+
end
|
94
|
+
|
95
|
+
def Mail.read(filename)
|
96
|
+
Mail.new(File.read(filename))
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
def Mail.random_tag
|
102
|
+
t = Time.now
|
103
|
+
sprintf('%x%x_%x%x%d%x',
|
104
|
+
t.to_i, t.tv_usec,
|
105
|
+
$$, Thread.current.object_id, Mail.uniq, rand(255))
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def Mail.something_random
|
111
|
+
(Thread.current.object_id * rand(255) / Time.now.to_f).to_s.slice(-3..-1).to_i
|
112
|
+
end
|
113
|
+
|
114
|
+
def Mail.uniq
|
115
|
+
@@uniq += 1
|
116
|
+
end
|
117
|
+
|
118
|
+
@@uniq = Mail.something_random
|
119
|
+
|
120
|
+
end
|
data/lib/mail/message.rb
ADDED
@@ -0,0 +1,648 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mail
|
3
|
+
# The Message class provides a single point of access to all things to do with an
|
4
|
+
# email message.
|
5
|
+
#
|
6
|
+
# You create a new email message by calling the Mail::Message.new method, or just
|
7
|
+
# Mail.new
|
8
|
+
#
|
9
|
+
# A Message object by default has the following objects inside it:
|
10
|
+
#
|
11
|
+
# * A Header object which contians all information and settings of the header of the email
|
12
|
+
# * Body object which contains all parts of the email that are not part of the header, this
|
13
|
+
# includes any attachments, body text, mime parts etc.
|
14
|
+
#
|
15
|
+
# ==Per RFC2822
|
16
|
+
#
|
17
|
+
# 2.1. General Description
|
18
|
+
#
|
19
|
+
# At the most basic level, a message is a series of characters. A
|
20
|
+
# message that is conformant with this standard is comprised of
|
21
|
+
# characters with values in the range 1 through 127 and interpreted as
|
22
|
+
# US-ASCII characters [ASCII]. For brevity, this document sometimes
|
23
|
+
# refers to this range of characters as simply "US-ASCII characters".
|
24
|
+
#
|
25
|
+
# Note: This standard specifies that messages are made up of characters
|
26
|
+
# in the US-ASCII range of 1 through 127. There are other documents,
|
27
|
+
# specifically the MIME document series [RFC2045, RFC2046, RFC2047,
|
28
|
+
# RFC2048, RFC2049], that extend this standard to allow for values
|
29
|
+
# outside of that range. Discussion of those mechanisms is not within
|
30
|
+
# the scope of this standard.
|
31
|
+
#
|
32
|
+
# Messages are divided into lines of characters. A line is a series of
|
33
|
+
# characters that is delimited with the two characters carriage-return
|
34
|
+
# and line-feed; that is, the carriage return (CR) character (ASCII
|
35
|
+
# value 13) followed immediately by the line feed (LF) character (ASCII
|
36
|
+
# value 10). (The carriage-return/line-feed pair is usually written in
|
37
|
+
# this document as "CRLF".)
|
38
|
+
#
|
39
|
+
# A message consists of header fields (collectively called "the header
|
40
|
+
# of the message") followed, optionally, by a body. The header is a
|
41
|
+
# sequence of lines of characters with special syntax as defined in
|
42
|
+
# this standard. The body is simply a sequence of characters that
|
43
|
+
# follows the header and is separated from the header by an empty line
|
44
|
+
# (i.e., a line with nothing preceding the CRLF).
|
45
|
+
class Message
|
46
|
+
|
47
|
+
include Patterns
|
48
|
+
include Utilities
|
49
|
+
include Deliverable
|
50
|
+
include Retrievable
|
51
|
+
|
52
|
+
# Creates a new Mail::Message object through .new
|
53
|
+
def initialize(*args, &block)
|
54
|
+
self.raw_source = args.flatten[0].to_s.strip
|
55
|
+
set_envelope_header
|
56
|
+
parse_message
|
57
|
+
separate_parts if multipart?
|
58
|
+
if block_given?
|
59
|
+
instance_eval(&block)
|
60
|
+
end
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
# Provides access to the raw source of the message as it was when it
|
65
|
+
# was instantiated. This is set at initialization and so is untouched
|
66
|
+
# by the parsers or decoder / encoders
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
#
|
70
|
+
# mail = Mail.new('This is an invalid email message')
|
71
|
+
# mail.raw_source #=> "This is an invalid email message"
|
72
|
+
def raw_source
|
73
|
+
@raw_source
|
74
|
+
end
|
75
|
+
|
76
|
+
def raw_source=(value)
|
77
|
+
@raw_source = value.to_crlf
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_envelope( val )
|
81
|
+
@raw_envelope = val
|
82
|
+
@envelope = Mail::Envelope.new( val )
|
83
|
+
end
|
84
|
+
|
85
|
+
def set_envelope( val )
|
86
|
+
@raw_envelope = val
|
87
|
+
@envelope = Mail::Envelope.new( val )
|
88
|
+
end
|
89
|
+
|
90
|
+
# The raw_envelope is the From mikel@test.lindsaar.net Mon May 2 16:07:05 2009
|
91
|
+
# type field that you can see at the top of any email that has come
|
92
|
+
# from a mailbox
|
93
|
+
def raw_envelope
|
94
|
+
@raw_envelope
|
95
|
+
end
|
96
|
+
|
97
|
+
def envelope_from
|
98
|
+
@envelope ? @envelope.from : nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def envelope_date
|
102
|
+
@envelope ? @envelope.date : nil
|
103
|
+
end
|
104
|
+
|
105
|
+
# Sets the header of the message object.
|
106
|
+
#
|
107
|
+
# Example:
|
108
|
+
#
|
109
|
+
# mail.header = 'To: mikel@test.lindsaar.net\r\nFrom: Bob@bob.com'
|
110
|
+
# mail.header #=> <#Mail::Header
|
111
|
+
def header=(value)
|
112
|
+
@header = Mail::Header.new(value)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns the header object of the message object. Or, if passed
|
116
|
+
# a parameter sets the value.
|
117
|
+
#
|
118
|
+
# Example:
|
119
|
+
#
|
120
|
+
# mail = Mail::Message.new('To: mikel\r\nFrom: you')
|
121
|
+
# mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr...
|
122
|
+
#
|
123
|
+
# mail.header #=> nil
|
124
|
+
# mail.header 'To: mikel\r\nFrom: you'
|
125
|
+
# mail.header #=> #<Mail::Header:0x13ce14 @raw_source="To: mikel\r\nFr...
|
126
|
+
def header(value = nil)
|
127
|
+
value ? self.header = value : @header
|
128
|
+
end
|
129
|
+
|
130
|
+
# Sets the body object of the message object.
|
131
|
+
#
|
132
|
+
# Example:
|
133
|
+
#
|
134
|
+
# mail.body = 'This is the body'
|
135
|
+
# mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo...
|
136
|
+
def body=(value)
|
137
|
+
@body = Mail::Body.new(value)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns the body of the message object. Or, if passed
|
141
|
+
# a parameter sets the value.
|
142
|
+
#
|
143
|
+
# Example:
|
144
|
+
#
|
145
|
+
# mail = Mail::Message.new('To: mikel\r\n\r\nThis is the body')
|
146
|
+
# mail.body #=> #<Mail::Body:0x13919c @raw_source="This is the bo...
|
147
|
+
#
|
148
|
+
# mail.body 'This is another body'
|
149
|
+
# mail.body #=> #<Mail::Body:0x13919c @raw_source="This is anothe...
|
150
|
+
def body(value = nil)
|
151
|
+
value ? self.body = value : @body
|
152
|
+
end
|
153
|
+
|
154
|
+
# Sets the to filed of the message header.
|
155
|
+
#
|
156
|
+
# Example:
|
157
|
+
#
|
158
|
+
# mail.to = '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
|
159
|
+
# mail.to #=> '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
|
160
|
+
def to=(value)
|
161
|
+
header[:to] = value
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns the to field in the message header. Or, if passed
|
165
|
+
# a parameter sets the value.
|
166
|
+
#
|
167
|
+
# Example:
|
168
|
+
#
|
169
|
+
# mail.to = '"M L" <mikel@test.lindsaar.net>'
|
170
|
+
# mail.to.to_s #=> '"M L" <mikel@test.lindsaar.net>'
|
171
|
+
# mail.to.formatted #=> ['"M L" <mikel@test.lindsaar.net>']
|
172
|
+
# mail.to.addresses #=> ['mikel@test.lindsaar.net']
|
173
|
+
def to(value = nil)
|
174
|
+
value ? self.to = value : header[:to]
|
175
|
+
end
|
176
|
+
|
177
|
+
# Sets the from field in the message header.
|
178
|
+
#
|
179
|
+
# Example:
|
180
|
+
#
|
181
|
+
# mail.from = '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
|
182
|
+
# mail.from.to_s #=> '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
|
183
|
+
def from=(value)
|
184
|
+
header[:from] = value
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns the from field in the message header. Or, if passed
|
188
|
+
# a parameter sets the value.
|
189
|
+
#
|
190
|
+
# Example:
|
191
|
+
#
|
192
|
+
# mail.from = '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
|
193
|
+
# mail.from.to_s #=> '"Mikel Lindsaar" <mikel@test.lindsaar.net>'
|
194
|
+
#
|
195
|
+
# mail.from '"M L" <mikel@test.lindsaar.net>'
|
196
|
+
# mail.from.to_s #=> '"M L" <mikel@test.lindsaar.net>'
|
197
|
+
# mail.from.formatted #=> ['"M L" <mikel@test.lindsaar.net>']
|
198
|
+
# mail.from.addresses #=> ['mikel@test.lindsaar.net']
|
199
|
+
def from(value = nil)
|
200
|
+
value ? self.from = value : header[:from]
|
201
|
+
end
|
202
|
+
|
203
|
+
# Sets the subject field in the message header.
|
204
|
+
#
|
205
|
+
# Example:
|
206
|
+
#
|
207
|
+
# mail.subject = 'This is the subject'
|
208
|
+
# mail.subject.to_s #=> 'This is the subject'
|
209
|
+
def subject=(value)
|
210
|
+
header[:subject] = value
|
211
|
+
end
|
212
|
+
|
213
|
+
# Returns the subject field in the message header. Or, if passed
|
214
|
+
# a parameter sets the value.
|
215
|
+
#
|
216
|
+
# Example:
|
217
|
+
#
|
218
|
+
# mail.subject = 'This is the subject'
|
219
|
+
# mail.subject.to_s #=> 'This is the subject'
|
220
|
+
#
|
221
|
+
# mail.subject 'This is another subject'
|
222
|
+
# mail.subject.to_s #=> 'This is another subject'
|
223
|
+
def subject(value = nil)
|
224
|
+
value ? self.subject = value : header[:subject]
|
225
|
+
end
|
226
|
+
|
227
|
+
# Allows you to add an arbitrary header
|
228
|
+
#
|
229
|
+
# Example:
|
230
|
+
#
|
231
|
+
# mail['foo'] = '1234'
|
232
|
+
# mail['foo'].to_s #=> '1234'
|
233
|
+
def []=(name, value)
|
234
|
+
if name.to_s == 'body'
|
235
|
+
self.body = value
|
236
|
+
else
|
237
|
+
header[underscoreize(name)] = value
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Allows you to read an arbitrary header
|
242
|
+
#
|
243
|
+
# Example:
|
244
|
+
#
|
245
|
+
# mail['foo'] = '1234'
|
246
|
+
# mail['foo'].to_s #=> '1234'
|
247
|
+
def [](name)
|
248
|
+
header[underscoreize(name)]
|
249
|
+
end
|
250
|
+
|
251
|
+
# Method Missing in this implementation allows you to set any of the
|
252
|
+
# standard fields directly as you would the "to", "subject" etc.
|
253
|
+
#
|
254
|
+
# Those fields used most often (to, subject et al) are given their
|
255
|
+
# own method for ease of documentation and also to avoid the hook
|
256
|
+
# call to method missing.
|
257
|
+
#
|
258
|
+
# This will only catch the known fields listed in:
|
259
|
+
#
|
260
|
+
# Mail::Field::KNOWN_FIELDS
|
261
|
+
#
|
262
|
+
# as per RFC 2822, any ruby string or method name could pretty much
|
263
|
+
# be a field name, so we don't want to just catch ANYTHING sent to
|
264
|
+
# a message object and interpret it as a header.
|
265
|
+
#
|
266
|
+
# This method provides all three types of header call to set, read
|
267
|
+
# and explicitly set with the = operator
|
268
|
+
#
|
269
|
+
# Examples:
|
270
|
+
#
|
271
|
+
# mail.comments = 'These are some comments'
|
272
|
+
# mail.comments.to_s #=> 'These are some comments'
|
273
|
+
#
|
274
|
+
# mail.comments 'These are other comments'
|
275
|
+
# mail.comments.to_s #=> 'These are other comments'
|
276
|
+
#
|
277
|
+
#
|
278
|
+
# mail.date = 'Tue, 1 Jul 2003 10:52:37 +0200'
|
279
|
+
# mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200'
|
280
|
+
#
|
281
|
+
# mail.date 'Tue, 1 Jul 2003 10:52:37 +0200'
|
282
|
+
# mail.date.to_s #=> 'Tue, 1 Jul 2003 10:52:37 +0200'
|
283
|
+
#
|
284
|
+
#
|
285
|
+
# mail.resent_msg_id = '<1234@resent_msg_id.lindsaar.net>'
|
286
|
+
# mail.resent_msg_id.to_s #=> '<1234@resent_msg_id.lindsaar.net>'
|
287
|
+
#
|
288
|
+
# mail.resent_msg_id '<4567@resent_msg_id.lindsaar.net>'
|
289
|
+
# mail.resent_msg_id.to_s #=> '<4567@resent_msg_id.lindsaar.net>'
|
290
|
+
def method_missing(name, *args, &block)
|
291
|
+
#:nodoc:
|
292
|
+
# Only take the structured fields, as we could take _anything_ really
|
293
|
+
# as it could become an optional field... "but therin lies the dark side"
|
294
|
+
field_name = underscoreize(name).chomp("=")
|
295
|
+
if Mail::Field::KNOWN_FIELDS.include?(field_name)
|
296
|
+
if args.empty?
|
297
|
+
header[field_name]
|
298
|
+
else
|
299
|
+
header[field_name] = args.first
|
300
|
+
end
|
301
|
+
else
|
302
|
+
super # otherwise pass it on
|
303
|
+
end
|
304
|
+
#:startdoc:
|
305
|
+
end
|
306
|
+
|
307
|
+
# Returns an FieldList of all the fields in the header in the order that
|
308
|
+
# they appear in the header
|
309
|
+
def header_fields
|
310
|
+
header.fields
|
311
|
+
end
|
312
|
+
|
313
|
+
# Returns true if the message has a message ID field, the field may or may
|
314
|
+
# not have a value, but the field exists or not.
|
315
|
+
def has_message_id?
|
316
|
+
header.has_message_id?
|
317
|
+
end
|
318
|
+
|
319
|
+
# Returns true if the message has a Date field, the field may or may
|
320
|
+
# not have a value, but the field exists or not.
|
321
|
+
def has_date?
|
322
|
+
header.has_date?
|
323
|
+
end
|
324
|
+
|
325
|
+
# Returns true if the message has a Date field, the field may or may
|
326
|
+
# not have a value, but the field exists or not.
|
327
|
+
def has_mime_version?
|
328
|
+
header.has_mime_version?
|
329
|
+
end
|
330
|
+
|
331
|
+
def has_content_type?
|
332
|
+
!!content_type
|
333
|
+
end
|
334
|
+
|
335
|
+
def has_charset?
|
336
|
+
!!charset
|
337
|
+
end
|
338
|
+
|
339
|
+
def has_transfer_encoding?
|
340
|
+
transfer_encoding
|
341
|
+
end
|
342
|
+
|
343
|
+
# Creates a new empty Message-ID field and inserts it in the correct order
|
344
|
+
# into the Header. The MessageIdField object will automatically generate
|
345
|
+
# a unique message ID if you try and encode it or output it to_s without
|
346
|
+
# specifying a message id.
|
347
|
+
#
|
348
|
+
# It will preserve the message ID you specify if you do.
|
349
|
+
def add_message_id(msg_id_val = '')
|
350
|
+
header['message-id'] = msg_id_val
|
351
|
+
end
|
352
|
+
|
353
|
+
# Creates a new empty Date field and inserts it in the correct order
|
354
|
+
# into the Header. The DateField object will automatically generate
|
355
|
+
# DateTime.now's date if you try and encode it or output it to_s without
|
356
|
+
# specifying a date yourself.
|
357
|
+
#
|
358
|
+
# It will preserve any date you specify if you do.
|
359
|
+
def add_date(date_val = '')
|
360
|
+
header['date'] = date_val
|
361
|
+
end
|
362
|
+
|
363
|
+
# Creates a new empty Mime Version field and inserts it in the correct order
|
364
|
+
# into the Header. The MimeVersion object will automatically generate
|
365
|
+
# DateTime.now's date if you try and encode it or output it to_s without
|
366
|
+
# specifying a date yourself.
|
367
|
+
#
|
368
|
+
# It will preserve any date you specify if you do.
|
369
|
+
def add_mime_version(ver_val = '')
|
370
|
+
header['mime-version'] = ver_val
|
371
|
+
end
|
372
|
+
|
373
|
+
# Adds a content type and charset if the body is US-ASCII
|
374
|
+
#
|
375
|
+
# Otherwise raises a warning
|
376
|
+
def add_content_type
|
377
|
+
header['Content-Type'] = 'text/plain'
|
378
|
+
end
|
379
|
+
|
380
|
+
# Adds a content type and charset if the body is US-ASCII
|
381
|
+
#
|
382
|
+
# Otherwise raises a warning
|
383
|
+
def add_charset
|
384
|
+
if body.only_us_ascii?
|
385
|
+
content_type.parameters['charset'] = 'US-ASCII'
|
386
|
+
else
|
387
|
+
STDERR.puts("Non US-ASCII detected and no charset defined.\nDefaulting to UTF-8, set your own if this is incorrect.")
|
388
|
+
content_type.parameters['charset'] = 'UTF-8'
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# Adds a transfer encoding
|
393
|
+
#
|
394
|
+
# Otherwise raises a warning
|
395
|
+
def add_transfer_encoding
|
396
|
+
if body.only_us_ascii?
|
397
|
+
header['Content-Transfer-Encoding'] = '7bit'
|
398
|
+
else
|
399
|
+
STDERR.puts("Non US-ASCII detected and no content-transfer-encoding defined.\nDefaulting to 8bit, set your own if this is incorrect.")
|
400
|
+
header['Content-Transfer-Encoding'] = '8bit'
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# Returns the content transfer encoding of the email
|
405
|
+
def transfer_encoding
|
406
|
+
content_transfer_encoding
|
407
|
+
end
|
408
|
+
|
409
|
+
# Returns the content type
|
410
|
+
def message_content_type
|
411
|
+
content_type ? content_type.content_type : nil
|
412
|
+
end
|
413
|
+
|
414
|
+
# Returns the character set defined in the content type field
|
415
|
+
def charset
|
416
|
+
content_type ? content_type.parameters['charset'] : nil
|
417
|
+
end
|
418
|
+
|
419
|
+
# Returns the main content type
|
420
|
+
def main_type
|
421
|
+
has_content_type? ? content_type.main_type : nil
|
422
|
+
end
|
423
|
+
|
424
|
+
# Returns the sub content type
|
425
|
+
def sub_type
|
426
|
+
has_content_type? ? content_type.sub_type : nil
|
427
|
+
end
|
428
|
+
|
429
|
+
# Returns the content type parameters
|
430
|
+
def mime_parameters
|
431
|
+
has_content_type? ? content_type.parameters : nil
|
432
|
+
end
|
433
|
+
|
434
|
+
# Returns true if the message is multipart
|
435
|
+
def multipart?
|
436
|
+
!!(main_type =~ /^multipart$/i)
|
437
|
+
end
|
438
|
+
|
439
|
+
# Returns true if the message is a multipart/report
|
440
|
+
def multipart_report?
|
441
|
+
multipart? && sub_type =~ /^report$/i
|
442
|
+
end
|
443
|
+
|
444
|
+
# Returns true if the message is a multipart/report; report-type=delivery-status;
|
445
|
+
def delivery_status_report?
|
446
|
+
multipart_report? && mime_parameters['report-type'] =~ /^delivery-status$/i
|
447
|
+
end
|
448
|
+
|
449
|
+
# returns the part in a multipart/report email that has the content-type delivery-status
|
450
|
+
def delivery_status_part
|
451
|
+
@delivery_stats_part ||= parts.select { |p| p.delivery_status_report_part? }.first
|
452
|
+
end
|
453
|
+
|
454
|
+
def bounced?
|
455
|
+
delivery_status_part.bounced?
|
456
|
+
end
|
457
|
+
|
458
|
+
def action
|
459
|
+
delivery_status_part.action
|
460
|
+
end
|
461
|
+
|
462
|
+
def final_recipient
|
463
|
+
delivery_status_part.final_recipient
|
464
|
+
end
|
465
|
+
|
466
|
+
def error_status
|
467
|
+
delivery_status_part.error_status
|
468
|
+
end
|
469
|
+
|
470
|
+
def diagnostic_code
|
471
|
+
delivery_status_part.diagnostic_code
|
472
|
+
end
|
473
|
+
|
474
|
+
def remote_mta
|
475
|
+
delivery_status_part.remote_mta
|
476
|
+
end
|
477
|
+
|
478
|
+
def retryable?
|
479
|
+
delivery_status_part.retryable?
|
480
|
+
end
|
481
|
+
|
482
|
+
# Returns the current boundary for this message part
|
483
|
+
def boundary
|
484
|
+
mime_parameters ? mime_parameters['boundary'] : nil
|
485
|
+
end
|
486
|
+
|
487
|
+
# Returns an array of parts in the message
|
488
|
+
def parts
|
489
|
+
body.parts
|
490
|
+
end
|
491
|
+
|
492
|
+
def attachments
|
493
|
+
body.parts.select { |p| p.attachment? }.map { |p| p.attachment }
|
494
|
+
end
|
495
|
+
|
496
|
+
# Accessor for html_part
|
497
|
+
def html_part(&block)
|
498
|
+
if block_given?
|
499
|
+
@html_part = Mail::Part.new(&block)
|
500
|
+
add_part(@html_part)
|
501
|
+
else
|
502
|
+
@html_part
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
# Accessor for text_part
|
507
|
+
def text_part(&block)
|
508
|
+
if block_given?
|
509
|
+
@text_part = Mail::Part.new(&block)
|
510
|
+
add_part(@text_part)
|
511
|
+
else
|
512
|
+
@text_part
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
# Helper to add a html part to a multipart/alternative email. If this and
|
517
|
+
# text_part are both defined in a message, then it will be a multipart/alternative
|
518
|
+
# message and set itself that way.
|
519
|
+
def html_part=(msg = nil)
|
520
|
+
if msg
|
521
|
+
@html_part = msg
|
522
|
+
else
|
523
|
+
@html_part = Mail::Part.new('Content-Type: text/html;')
|
524
|
+
end
|
525
|
+
add_part(@html_part)
|
526
|
+
end
|
527
|
+
|
528
|
+
# Helper to add a text part to a multipart/alternative email. If this and
|
529
|
+
# html_part are both defined in a message, then it will be a multipart/alternative
|
530
|
+
# message and set itself that way.
|
531
|
+
def text_part=(msg = nil)
|
532
|
+
if msg
|
533
|
+
@text_part = msg
|
534
|
+
else
|
535
|
+
@text_part = Mail::Part.new('Content-Type: text/plain;')
|
536
|
+
end
|
537
|
+
add_part(@text_part)
|
538
|
+
end
|
539
|
+
|
540
|
+
# Adds a part to the parts list or creates the part list
|
541
|
+
def add_part(part)
|
542
|
+
add_multipart_alternate_header
|
543
|
+
self.body << part
|
544
|
+
end
|
545
|
+
|
546
|
+
# Adds a part to the parts list or creates the part list
|
547
|
+
def add_file(options)
|
548
|
+
add_multipart_mixed_header
|
549
|
+
if options.is_a?(Hash)
|
550
|
+
self.body << Mail::Part.new(options)
|
551
|
+
else
|
552
|
+
self.body << Mail::Part.new(:filename => options)
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
# Encodes the message, calls encode on all it's parts, gets an email message
|
557
|
+
# ready to send
|
558
|
+
def encode!
|
559
|
+
parts.each { |part| part.encode! }
|
560
|
+
add_required_fields
|
561
|
+
end
|
562
|
+
|
563
|
+
# Decodes the message, calls decode on all it's parts, gets an email message
|
564
|
+
# ready to send
|
565
|
+
def decode!
|
566
|
+
parts.each { |part| part.decode! }
|
567
|
+
add_required_fields
|
568
|
+
end
|
569
|
+
|
570
|
+
# Outputs an encoded string representation of the mail message including
|
571
|
+
# all headers, attachments, etc. This is an encoded email in US-ASCII,
|
572
|
+
# so it is able to be directly sent to an email server.
|
573
|
+
def encoded
|
574
|
+
encode!
|
575
|
+
buffer = header.encoded
|
576
|
+
buffer << "\r\n"
|
577
|
+
buffer << body.encoded
|
578
|
+
buffer
|
579
|
+
end
|
580
|
+
|
581
|
+
alias :to_s :encoded
|
582
|
+
|
583
|
+
private
|
584
|
+
|
585
|
+
# 2.1. General Description
|
586
|
+
# A message consists of header fields (collectively called "the header
|
587
|
+
# of the message") followed, optionally, by a body. The header is a
|
588
|
+
# sequence of lines of characters with special syntax as defined in
|
589
|
+
# this standard. The body is simply a sequence of characters that
|
590
|
+
# follows the header and is separated from the header by an empty line
|
591
|
+
# (i.e., a line with nothing preceding the CRLF).
|
592
|
+
#
|
593
|
+
# Additionally, I allow for the case where someone might have put whitespace
|
594
|
+
# on the "gap line"
|
595
|
+
def parse_message
|
596
|
+
header_part, body_part = raw_source.split(/#{CRLF}#{WSP}*#{CRLF}/m, 2)
|
597
|
+
self.header = header_part
|
598
|
+
self.body = body_part
|
599
|
+
end
|
600
|
+
|
601
|
+
def set_envelope_header
|
602
|
+
if match_data = raw_source.to_s.match(/From\s(#{TEXT}+)#{CRLF}(.*)/m)
|
603
|
+
set_envelope(match_data[1])
|
604
|
+
self.raw_source = match_data[2]
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
def separate_parts
|
609
|
+
body.split!(boundary)
|
610
|
+
end
|
611
|
+
|
612
|
+
def add_required_fields
|
613
|
+
@body = Mail::Body.new('') if body.nil?
|
614
|
+
add_message_id unless (has_message_id? || self.class == Mail::Part)
|
615
|
+
add_date unless has_date?
|
616
|
+
add_mime_version unless has_mime_version?
|
617
|
+
add_content_type unless has_content_type?
|
618
|
+
add_charset unless has_charset?
|
619
|
+
add_transfer_encoding unless has_transfer_encoding?
|
620
|
+
end
|
621
|
+
|
622
|
+
def add_multipart_alternate_header
|
623
|
+
if html_part && text_part
|
624
|
+
unless header['content-type']
|
625
|
+
header['content-type'] = ContentTypeField.with_boundary('multipart/alternative').value
|
626
|
+
body.boundary = boundary
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
def add_multipart_mixed_header
|
632
|
+
unless header['content-type']
|
633
|
+
header['content-type'] = ContentTypeField.with_boundary('multipart/mixed').value
|
634
|
+
body.boundary = boundary
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
class << self
|
639
|
+
|
640
|
+
# Only POP3 is supported for now
|
641
|
+
def get_all_mail(&block)
|
642
|
+
self.pop3_get_all_mail(&block)
|
643
|
+
end
|
644
|
+
|
645
|
+
end
|
646
|
+
|
647
|
+
end
|
648
|
+
end
|