mailfactory 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/mailfactory.rb +309 -0
- data/lib/test.rb +21 -0
- data/tests/test_mailfactory.rb +96 -0
- metadata +50 -0
data/lib/mailfactory.rb
ADDED
@@ -0,0 +1,309 @@
|
|
1
|
+
# = Overview:
|
2
|
+
# A simple to use module for generating RFC compliant MIME mail
|
3
|
+
# ---
|
4
|
+
# = License:
|
5
|
+
# Author:: David Powers
|
6
|
+
# Copyright:: May, 2005
|
7
|
+
# License:: Ruby License
|
8
|
+
# ---
|
9
|
+
# = Usage:
|
10
|
+
# require 'net/smtp'
|
11
|
+
# require 'rubygems'
|
12
|
+
# require 'mailfactory'
|
13
|
+
#
|
14
|
+
#
|
15
|
+
# mail = MailFactory.new()
|
16
|
+
# mail.to = "test@test.com"
|
17
|
+
# mail.from = "sender@sender.com"
|
18
|
+
# mail.subject = "Here are some files for you!"
|
19
|
+
# mail.text = "This is what people with plain text mail readers will see"
|
20
|
+
# mail.html = "A little something <b>special</b> for people with HTML readers"
|
21
|
+
# mail.attach("/etc/fstab")
|
22
|
+
# mail.attach("/some/other/file")
|
23
|
+
#
|
24
|
+
# Net::SMTP.start('smtp1.testmailer.com', 25, 'mail.from.domain', fromaddress, password, :cram_md5) { |smtp|
|
25
|
+
# mail.to = toaddress
|
26
|
+
# smtp.send_message(mail.to_s(), fromaddress, toaddress)
|
27
|
+
# }
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
require 'base64'
|
32
|
+
require 'pathname'
|
33
|
+
|
34
|
+
# try to bring in the mime/types module, make a dummy module if it can't be found
|
35
|
+
begin
|
36
|
+
begin
|
37
|
+
require 'rubygems'
|
38
|
+
rescue LoadError
|
39
|
+
end
|
40
|
+
require 'mime/types'
|
41
|
+
rescue LoadError
|
42
|
+
module MIME
|
43
|
+
class Types
|
44
|
+
def Types::type_for(filename)
|
45
|
+
return('')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# An easy class for creating a mail message
|
52
|
+
class MailFactory
|
53
|
+
|
54
|
+
def initialize()
|
55
|
+
@headers = Array.new()
|
56
|
+
@attachments = Array.new()
|
57
|
+
@attachmentboundary = generate_boundary()
|
58
|
+
@bodyboundary = generate_boundary()
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# adds a header to the bottom of the headers
|
63
|
+
def add_header(header, value)
|
64
|
+
@headers << "#{header}: #{value}"
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# removes the named header - case insensitive
|
69
|
+
def remove_header(header)
|
70
|
+
@headers.each_index() { |i|
|
71
|
+
if(@headers[i] =~ /^#{Regexp.escape(header)}:/i)
|
72
|
+
@headers.delete_at(i)
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# sets a header (removing any other versions of that header)
|
79
|
+
def set_header(header, value)
|
80
|
+
remove_header(header)
|
81
|
+
add_header(header, value)
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def to=(newto)
|
86
|
+
remove_header("To")
|
87
|
+
add_header("To", newto)
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def to()
|
92
|
+
return(get_header("To")[0])
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def from=(newfrom)
|
97
|
+
remove_header("From")
|
98
|
+
add_header("From", newfrom)
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def from()
|
103
|
+
return(get_header("From")[0])
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def subject=(newsubject)
|
108
|
+
remove_header("Subject")
|
109
|
+
add_header("Subject", newsubject)
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def subject()
|
114
|
+
return(get_header("Subject")[0])
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def cc=(newcc)
|
119
|
+
remove_header("CC")
|
120
|
+
add_header("CC", newcc)
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def cc()
|
125
|
+
return(get_header("CC")[0])
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# sets the plain text body of the message
|
130
|
+
def text=(newtext)
|
131
|
+
@text = newtext
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
# sets the HTML body of the message. Only the body of the
|
136
|
+
# html should be provided
|
137
|
+
def html=(newhtml)
|
138
|
+
@html = "<html>\n<head>\n<meta content=\"text/html;charset=ISO-8859-1\" http-equiv=\"Content-Type\">\n</head>\n<body bgcolor=\"#ffffff\" text=\"#000000\">\n#{newhtml}\n</body>\n</html>"
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
# sets the HTML body of the message. The entire HTML section should be provided
|
143
|
+
def rawhtml=(newhtml)
|
144
|
+
@html = newhtml
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
# returns the value (or values) of the named header in an array
|
149
|
+
def get_header(header)
|
150
|
+
headers = Array.new()
|
151
|
+
headerregex = /^#{Regexp.escape(header)}:/i
|
152
|
+
@headers.each() { |h|
|
153
|
+
if(headerregex.match(h))
|
154
|
+
headers << h[/^[^:]+:(.*)/i, 1].strip()
|
155
|
+
end
|
156
|
+
}
|
157
|
+
|
158
|
+
return(headers)
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
# returns true if the email is multipart
|
163
|
+
def multipart?()
|
164
|
+
if(@attachments.length > 0 or @html != nil)
|
165
|
+
return(true)
|
166
|
+
else
|
167
|
+
return(false)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
# returns a formatted email
|
173
|
+
def to_s()
|
174
|
+
if(get_header("Date").length == 0)
|
175
|
+
add_header("Date", Time.now.strftime("%a, %d %B %Y %H:%M:%S %Z"))
|
176
|
+
end
|
177
|
+
|
178
|
+
# Add a mime header if we don't already have one and we have multiple parts
|
179
|
+
if(multipart?())
|
180
|
+
if(get_header("MIME-Version").length == 0)
|
181
|
+
add_header("MIME-Version", "1.0")
|
182
|
+
end
|
183
|
+
|
184
|
+
if(get_header("Content-Type").length == 0)
|
185
|
+
if(@attachments.length == 0)
|
186
|
+
add_header("Content-Type", "multipart/alternative;boundary=\"#{@bodyboundary}\"")
|
187
|
+
else
|
188
|
+
add_header("Content-Type", "multipart/mixed; boundary=\"#{@attachmentboundary}\"")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
return("#{headers_to_s()}#{body_to_s()}")
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
# generates a unique boundary string
|
198
|
+
def generate_boundary()
|
199
|
+
randomstring = Array.new()
|
200
|
+
1.upto(25) {
|
201
|
+
whichglyph = rand(100)
|
202
|
+
if(whichglyph < 40)
|
203
|
+
randomstring << (rand(25) + 65).chr()
|
204
|
+
elsif(whichglyph < 70)
|
205
|
+
randomstring << (rand(25) + 97).chr()
|
206
|
+
elsif(whichglyph < 90)
|
207
|
+
randomstring << (rand(10) + 48).chr()
|
208
|
+
elsif(whichglyph < 95)
|
209
|
+
randomstring << '.'
|
210
|
+
else
|
211
|
+
randomstring << '_'
|
212
|
+
end
|
213
|
+
}
|
214
|
+
return("----=_NextPart_#{randomstring.join()}")
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
# adds an attachment to the mail. Type may be given as a mime type. If it
|
219
|
+
# is left off and the MIME::Types module is available it will be determined automagically.
|
220
|
+
def add_attachment(filename, type=nil)
|
221
|
+
attachment = Array.new()
|
222
|
+
attachment[0] = Pathname.new(filename).basename
|
223
|
+
attachment[1] = MIME::Types.type_for(filename).to_s
|
224
|
+
File.open(filename, File::RDONLY) { |fp|
|
225
|
+
attachment[2] = Base64.b64encode(fp.read())
|
226
|
+
}
|
227
|
+
@attachments << attachment
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
# adds an attachment to the mail as emailfilename. Type may be given as a mime type. If it
|
232
|
+
# is left off and the MIME::Types module is available it will be determined automagically.
|
233
|
+
def add_attachment_as(filename, emailfilename, type=nil)
|
234
|
+
attachment = Array.new()
|
235
|
+
attachment[0] = emailfilename
|
236
|
+
attachment[1] = MIME::Types.type_for(filename).to_s
|
237
|
+
File.open(filename, File::RDONLY) { |fp|
|
238
|
+
attachment[2] = Base64.b64encode(fp.read())
|
239
|
+
}
|
240
|
+
@attachments << attachment
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
alias attach add_attachment
|
245
|
+
alias attach_as add_attachment_as
|
246
|
+
|
247
|
+
protected
|
248
|
+
|
249
|
+
# returns the @headers as a properly formatted string
|
250
|
+
def headers_to_s()
|
251
|
+
return("#{@headers.join("\r\n")}\r\n\r\n")
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
# returns the body as a properly formatted string
|
256
|
+
def body_to_s()
|
257
|
+
body = Array.new()
|
258
|
+
|
259
|
+
# simple message with one part
|
260
|
+
if(!multipart?())
|
261
|
+
return(@text)
|
262
|
+
else
|
263
|
+
body << "This is a multi-part message in MIME format.\r\n\r\n--#{@attachmentboundary}\r\nContent-Type: multipart/alternative; boundary=\"#{@bodyboundary}\""
|
264
|
+
|
265
|
+
if(@attachments.length > 0)
|
266
|
+
# text part
|
267
|
+
body << "#{buildbodyboundary('text/plain; charset=ISO-8859-1; format=flowed', '7bit')}\r\n\r\n#{@text}"
|
268
|
+
|
269
|
+
# html part
|
270
|
+
body << "#{buildbodyboundary('text/html; charset=ISO-8859-1', '7bit')}\r\n\r\n#{@html}"
|
271
|
+
|
272
|
+
body << "--#{@bodyboundary}--"
|
273
|
+
|
274
|
+
# and, the attachments
|
275
|
+
if(@attachments.length > 0)
|
276
|
+
@attachments.each() { |attachment|
|
277
|
+
body << "#{buildattachmentboundary(attachment[1], 'base64', attachment[0])}\r\n\r\n#{attachment[2]}"
|
278
|
+
}
|
279
|
+
body << "\r\n--#{@attachmentboundary}--"
|
280
|
+
end
|
281
|
+
else
|
282
|
+
# text part
|
283
|
+
body << "#{buildbodyboundary('text/plain; charset=ISO-8859-1; format=flowed', '7bit')}\r\n\r\n#{@text}"
|
284
|
+
|
285
|
+
# html part
|
286
|
+
body << "#{buildbodyboundary('text/html; charset=ISO-8859-1', '7bit')}\r\n\r\n#{@html}"
|
287
|
+
|
288
|
+
body << "--#{@bodyboundary}--"
|
289
|
+
end
|
290
|
+
|
291
|
+
return(body.join("\r\n\r\n"))
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
# builds a boundary string for including attachments in the body
|
297
|
+
def buildattachmentboundary(type, encoding, filename)
|
298
|
+
disposition = "\r\nContent-Disposition: inline; filename=\"#{filename}\""
|
299
|
+
return("--#{@attachmentboundary}\r\nContent-Type: #{type}; name=\"#{filename}\"\r\nContent-Transfer-Encoding: #{encoding}#{disposition}")
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
# builds a boundary string for inclusion in the body of a message
|
304
|
+
def buildbodyboundary(type, encoding)
|
305
|
+
return("--#{@bodyboundary}\r\nContent-Type: #{type}\r\nContent-Transfer-Encoding: #{encoding}")
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
|
data/lib/test.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'net/smtp'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'mailfactory'
|
6
|
+
|
7
|
+
|
8
|
+
mail = MailFactory.new()
|
9
|
+
mail.to = "test@test.com"
|
10
|
+
mail.from = "sender@sender.com"
|
11
|
+
mail.subject = "Here are some files for you!"
|
12
|
+
mail.text = "This is what people with plain text mail readers will see"
|
13
|
+
mail.html = "A little something <b>special</b> for people with HTML readers"
|
14
|
+
mail.attach("/etc/fstab")
|
15
|
+
mail.attach("/some/other/file")
|
16
|
+
|
17
|
+
Net::SMTP.start('smtp1.testmailer.com', 25, 'mail.from.domain', fromaddress, password, :cram_md5) { |smtp|
|
18
|
+
mail.to = toaddress
|
19
|
+
smtp.send_message(mail.to_s(), fromaddress, toaddress)
|
20
|
+
}
|
21
|
+
|
@@ -0,0 +1,96 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require '../lib/mailfactory.rb'
|
5
|
+
|
6
|
+
class TC_MailFactory < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup()
|
9
|
+
@mail = MailFactory.new
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def test_set_to
|
14
|
+
assert_nothing_raised("exception raised while setting to=") {
|
15
|
+
@mail.to = "test@test.com"
|
16
|
+
}
|
17
|
+
|
18
|
+
assert_equal(@mail.to, "test@test.com", "to does not equal what it was set to")
|
19
|
+
|
20
|
+
assert_nothing_raised("exception raised while setting to=") {
|
21
|
+
@mail.to = "test@test2.com"
|
22
|
+
}
|
23
|
+
|
24
|
+
# count to headers in the final message to make sure we have only one
|
25
|
+
count = 0
|
26
|
+
@mail.to_s().each_line() { |line|
|
27
|
+
if(line =~ /^To:/i)
|
28
|
+
count = count + 1
|
29
|
+
end
|
30
|
+
}
|
31
|
+
assert_equal(1, count, "Count of To: headers expected to be 1, but was #{count}")
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_set_from
|
35
|
+
assert_nothing_raised("exception raised while setting from=") {
|
36
|
+
@mail.from = "test@test.com"
|
37
|
+
}
|
38
|
+
|
39
|
+
assert_equal(@mail.from, "test@test.com", "from does not equal what it was set to")
|
40
|
+
|
41
|
+
assert_nothing_raised("exception raised while setting from=") {
|
42
|
+
@mail.from = "test@test2.com"
|
43
|
+
}
|
44
|
+
|
45
|
+
# count to headers in the final message to make sure we have only one
|
46
|
+
count = 0
|
47
|
+
@mail.to_s().each_line() { |line|
|
48
|
+
if(line =~ /^From:/i)
|
49
|
+
count = count + 1
|
50
|
+
end
|
51
|
+
}
|
52
|
+
assert_equal(1, count, "Count of From: headers expected to be 1, but was #{count}")
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_set_subject
|
56
|
+
assert_nothing_raised("exception raised while setting subject=") {
|
57
|
+
@mail.subject = "Test Subject"
|
58
|
+
}
|
59
|
+
|
60
|
+
assert_equal(@mail.subject, "Test Subject", "subject does not equal what it was set to")
|
61
|
+
|
62
|
+
assert_nothing_raised("exception raised while setting subject=") {
|
63
|
+
@mail.subject = "A Different Subject"
|
64
|
+
}
|
65
|
+
|
66
|
+
# count to headers in the final message to make sure we have only one
|
67
|
+
count = 0
|
68
|
+
@mail.to_s().each_line() { |line|
|
69
|
+
if(line =~ /^Subject:/i)
|
70
|
+
count = count + 1
|
71
|
+
end
|
72
|
+
}
|
73
|
+
assert_equal(1, count, "Count of Subject: headers expected to be 1, but was #{count}")
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_set_header
|
77
|
+
assert_nothing_raised("exception raised while setting arbitrary header") {
|
78
|
+
@mail.set_header("arbitrary", "some value")
|
79
|
+
}
|
80
|
+
|
81
|
+
assert_equal("some value", @mail.get_header("arbitrary")[0], "arbitrary header does not equal \"some value\"")
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_boundary_generator
|
85
|
+
1.upto(50) {
|
86
|
+
assert_match(/^----=_NextPart_[a-zA-Z0-9\._]{25}$/, @mail.generate_boundary(), "illegal message boundary generated")
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_email
|
91
|
+
@mail.to="test@test.com"
|
92
|
+
@mail.from="test@othertest.com"
|
93
|
+
@mail.subject="This is a test"
|
94
|
+
@mail.text = "This is a test message with\na few\n\nlines."
|
95
|
+
end
|
96
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: mailfactory
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.5.0
|
7
|
+
date: 2005-05-21
|
8
|
+
summary: MailFactory is a pure-ruby MIME mail generator
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: david@grayskies.net
|
12
|
+
homepage: http://mailfactory.rubyforge.org
|
13
|
+
rubyforge_project: mailfactory
|
14
|
+
description: "MailFactory is s simple module for producing RFC compliant mail that can include
|
15
|
+
multiple attachments, multiple body parts, and arbitrary headers"
|
16
|
+
autorequire: mailfactory
|
17
|
+
default_executable:
|
18
|
+
bindir: bin
|
19
|
+
has_rdoc: true
|
20
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
21
|
+
requirements:
|
22
|
+
-
|
23
|
+
- ">"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.0.0
|
26
|
+
version:
|
27
|
+
platform: ruby
|
28
|
+
authors:
|
29
|
+
- David Powers
|
30
|
+
files:
|
31
|
+
- lib/mailfactory.rb
|
32
|
+
- lib/test.rb
|
33
|
+
test_files:
|
34
|
+
- tests/test_mailfactory.rb
|
35
|
+
rdoc_options: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
executables: []
|
38
|
+
extensions: []
|
39
|
+
requirements: []
|
40
|
+
dependencies:
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mime-types
|
43
|
+
version_requirement:
|
44
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
45
|
+
requirements:
|
46
|
+
-
|
47
|
+
- ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 1.13.1
|
50
|
+
version:
|