mailfactory 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: