mailit 2009.08

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS ADDED
@@ -0,0 +1,6 @@
1
+ Following persons have contributed to mailit.
2
+ (Sorted by number of submitted patches, then alphabetically)
3
+
4
+ 15 Michael Fellinger <mf@rubyists.com>
5
+ 2 Jake Douglas <jakecdouglas@gmail.com>
6
+ 1 Kevin Berry <kb@rubyists.com>
@@ -0,0 +1,76 @@
1
+ [78c844d | 2009-08-25 21:40:40 UTC] Michael Fellinger <m.fellinger@gmail.com>
2
+
3
+ * Another fix
4
+
5
+ [fb25c19 | 2009-08-25 21:40:01 UTC] Michael Fellinger <m.fellinger@gmail.com>
6
+
7
+ * Version 2009.08
8
+
9
+ [2e6d77a | 2009-08-25 21:38:53 UTC] Michael Fellinger <m.fellinger@gmail.com>
10
+
11
+ * Fix Rakefile
12
+
13
+ [585554d | 2009-08-25 05:11:42 UTC] Kevin Berry <kevin@opensourcealchemist.com>
14
+
15
+ * Take in Pistos's patch, make my own patch to get running for current project, and add seedling structure.
16
+
17
+ [db73002 | 2009-08-06 06:50:35 UTC] Jake Douglas <jakecdouglas@gmail.com>
18
+
19
+ * NetSMTP and EventMachine version both actually send mail now. Previous configuration didn't override the #send method correctly so the EM methods weren't loaded. The EM module may only be included now, not used with extend.
20
+
21
+ Signed-off-by: Michael Fellinger <m.fellinger@gmail.com>
22
+
23
+ [9a36e2f | 2009-08-06 05:39:26 UTC] Jake Douglas <jakecdouglas@gmail.com>
24
+
25
+ * get the value of :noop from the options hash in NetSmtp#send
26
+
27
+ Signed-off-by: Michael Fellinger <m.fellinger@gmail.com>
28
+
29
+ [c6da60b | 2009-06-08 13:52:33 UTC] Michael Fellinger <m.fellinger@gmail.com>
30
+
31
+ * Release new version
32
+
33
+ [185cf45 | 2009-06-08 13:51:07 UTC] Michael Fellinger <m.fellinger@gmail.com>
34
+
35
+ * Fix long outstanding issues
36
+
37
+ [e429f3b | 2009-04-12 13:38:16 UTC] Michael Fellinger <m.fellinger@gmail.com>
38
+
39
+ * Major refactor of Mailer, add support for EventMachine::SmtpClient and remove render_send
40
+
41
+ [341d76a | 2009-04-12 13:23:33 UTC] Michael Fellinger <m.fellinger@gmail.com>
42
+
43
+ * Make Mail#header_string and Mail#body_string public
44
+
45
+ [5716124 | 2009-03-15 14:26:27 UTC] Michael Fellinger <m.fellinger@gmail.com>
46
+
47
+ * Add auth_type and username options for Mailer#send, should work on windows now
48
+
49
+ [ff38041 | 2009-03-10 21:02:58 UTC] Michael Fellinger <m.fellinger@gmail.com>
50
+
51
+ * Add gemspec
52
+
53
+ [07b92d0 | 2009-03-10 20:49:36 UTC] Michael Fellinger <m.fellinger@gmail.com>
54
+
55
+ * Add Mailit::Mime - detects and uses available mime providers
56
+
57
+ [584398c | 2009-03-10 20:48:49 UTC] Michael Fellinger <m.fellinger@gmail.com>
58
+
59
+ * Little improvment to style
60
+
61
+ [194c990 | 2009-03-10 20:19:08 UTC] Michael Fellinger <m.fellinger@gmail.com>
62
+
63
+ * Add warning if no mime-type detection is possible
64
+
65
+ [184b20d | 2009-03-10 20:18:54 UTC] Michael Fellinger <m.fellinger@gmail.com>
66
+
67
+ * Add Version
68
+
69
+ [74e9ca3 | 2009-03-10 20:18:35 UTC] Michael Fellinger <m.fellinger@gmail.com>
70
+
71
+ * Add readme
72
+
73
+ [0a4bcad | 2009-03-10 20:02:28 UTC] Michael Fellinger <m.fellinger@gmail.com>
74
+
75
+ * Initial commit
76
+
@@ -0,0 +1,28 @@
1
+ AUTHORS
2
+ CHANGELOG
3
+ MANIFEST
4
+ README.md
5
+ Rakefile
6
+ lib/mailit.rb
7
+ lib/mailit/mail.rb
8
+ lib/mailit/mailer.rb
9
+ lib/mailit/mime.rb
10
+ lib/mailit/version.rb
11
+ lib/version.rb
12
+ mailit.gemspec
13
+ spec/helper.rb
14
+ spec/mailit/mail.rb
15
+ spec/mailit/mailer.rb
16
+ tasks/authors.rake
17
+ tasks/bacon.rake
18
+ tasks/changelog.rake
19
+ tasks/copyright.rake
20
+ tasks/gem.rake
21
+ tasks/gem_installer.rake
22
+ tasks/install_dependencies.rake
23
+ tasks/manifest.rake
24
+ tasks/rcov.rake
25
+ tasks/release.rake
26
+ tasks/reversion.rake
27
+ tasks/setup.rake
28
+ tasks/yard.rake
@@ -0,0 +1,63 @@
1
+ # Mailit
2
+
3
+ Mailit is a simple to use library to create and send MIME compliant e-mail with
4
+ attachments and various encodings.
5
+
6
+ This is a fork of MailFactory and provides a mostly identical API but has been
7
+ cleaned up, simplified, and made compliant to common Ruby idioms. I would like
8
+ to thank David Powers for the original MailFactory, it served me well for many
9
+ years.
10
+
11
+ Copyright (c) 2005-2008 David Powers.
12
+ Copyright (c) 2009 Michael Fellinger.
13
+
14
+ This program is free software. You can re-distribute and/or modify this program
15
+ under the same terms as Ruby itself.
16
+
17
+
18
+ ## Dependencies
19
+
20
+ Any Ruby since 1.8.4 should work.
21
+ Mailit can use the Rack or Mime::Types libraries to determine the mime-type of
22
+ attachments automatically, but they are optional.
23
+
24
+
25
+ ## Usage of Mailit::Mail
26
+
27
+ require 'net/smtp'
28
+ require 'mailit'
29
+
30
+ mail = Mailit::Mail.new
31
+ mail.to = 'test@test.com'
32
+ mail.from = 'sender@sender.com'
33
+ mail.subject 'Here are some files for you!'
34
+ mail.text = 'This is what people with plain text mail readers will see'
35
+ mail.html = "A little something <b>special</b> for people with HTML readers'
36
+ mail.attach('/etc/fstab')
37
+ mail.attach('/home/manveru/.vimrc')
38
+
39
+ puts mail
40
+
41
+
42
+ ## Usage of Mailit::Mailer
43
+
44
+ Using the mail variable from above example
45
+
46
+ mailer = Mailit::Mailer.new
47
+
48
+ mailer.send(mail, :server => 'smtp.example.com', :port => 25,
49
+ :domain => 'example.com', :password => 'foo')
50
+
51
+
52
+ ## Todo:
53
+
54
+ MailFactory has a method_missing that handles getting and setting of arbitrary
55
+ headers.
56
+ I went for the less magical #[] and #[]= methods, maybe someone can add the
57
+ MailFactory behaviour.
58
+
59
+ ## Thanks to
60
+
61
+ * Michael Thompson (AKA:nylon)
62
+
63
+ Making mailer work on windows
@@ -0,0 +1,84 @@
1
+ begin; require 'rubygems'; rescue LoadError; end
2
+
3
+ require 'rake'
4
+ require 'rake/clean'
5
+ require 'rake/gempackagetask'
6
+ require 'time'
7
+ require 'date'
8
+ require './lib/mailit'
9
+
10
+ PROJECT_SPECS = FileList[
11
+ 'spec/*/**/*.rb'
12
+ ]
13
+
14
+ PROJECT_MODULE = 'Mailit'
15
+ PROJECT_README = 'README'
16
+ #PROJECT_RUBYFORGE_GROUP_ID = 3034
17
+ PROJECT_COPYRIGHT_SUMMARY = [
18
+ "# Copyright (c) 2008-#{Time.now.year} The Rubyists, LLC (effortless systems) <rubyists@rubyists.com>",
19
+ "# Distributed under the terms of the MIT license.",
20
+ "# See the LICENSE file which accompanies this software for the full text",
21
+ "#"
22
+ ]
23
+ PROJECT_COPYRIGHT = PROJECT_COPYRIGHT_SUMMARY + [
24
+ "# Permission is hereby granted, free of charge, to any person obtaining a copy",
25
+ '# of this software and associated documentation files (the "Software"), to deal',
26
+ "# in the Software without restriction, including without limitation the rights",
27
+ "# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell",
28
+ "# copies of the Software, and to permit persons to whom the Software is",
29
+ "# furnished to do so, subject to the following conditions:",
30
+ "#",
31
+ "# The above copyright notice and this permission notice shall be included in",
32
+ "# all copies or substantial portions of the Software.",
33
+ "#",
34
+ '# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR',
35
+ "# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,",
36
+ "# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE",
37
+ "# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER",
38
+ "# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,",
39
+ "# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN",
40
+ "# THE SOFTWARE."
41
+ ]
42
+
43
+ PROJECT_VERSION =
44
+ if version = ENV['PROJECT_VERSION'] || ENV['VERSION']
45
+ version
46
+ else
47
+ ::VERSION rescue Date.today.strftime("%Y.%m.%d")
48
+ end
49
+
50
+ # To release the monthly version do:
51
+ # $ PROJECT_VERSION=2009.03 rake release
52
+
53
+ PROJECT_FILES = FileList[`git ls-files`.split("\n")].exclude('.gitignore')
54
+
55
+ GEMSPEC = Gem::Specification.new{|s|
56
+ s.name = "mailit"
57
+ s.author = "Kevin Berry"
58
+ s.summary = "The Mailit library, by Kevin Berry"
59
+ s.description = "The Mailit library, by Kevin Berry"
60
+ s.email = "kevinberry@nrs.us"
61
+ s.homepage = "http://github.com/manveru/mailit"
62
+ s.platform = Gem::Platform::RUBY
63
+ s.version = PROJECT_VERSION
64
+ s.files = PROJECT_FILES
65
+ s.has_rdoc = true
66
+ s.require_path = "lib"
67
+ }
68
+
69
+ Dir.glob('tasks/*.rake'){|f| import(f) }
70
+
71
+ task :default => [:bacon]
72
+
73
+ CLEAN.include %w[
74
+ **/.*.sw?
75
+ *.gem
76
+ .config
77
+ **/*~
78
+ **/{data.db,cache.yaml}
79
+ *.yaml
80
+ pkg
81
+ rdoc
82
+ ydoc
83
+ *coverage*
84
+ ]
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+
3
+ module Mailit
4
+ end
5
+
6
+ require 'mailit/mime'
7
+ require 'mailit/mail'
8
+ require 'mailit/mailer'
@@ -0,0 +1,367 @@
1
+ require 'time'
2
+ require 'pathname'
3
+ require 'enumerator' unless 'String'.respond_to?(:enum_for)
4
+
5
+ module Mailit
6
+
7
+ # = Overview:
8
+ #
9
+ # A simple to use class to generate RFC compliant MIME email.
10
+ #
11
+ # MailIt is a fork of MailFactory and provides a mostly identical API but has
12
+ # been cleaned up, simplified, and made compliant to common Ruby idioms.
13
+ #
14
+ # Copyright (c) 2005-2008 David Powers.
15
+ # Copyright (c) 2009 Michael Fellinger.
16
+ #
17
+ # This program is free software. You can re-distribute and/or
18
+ # modify this program under the same terms as Ruby itself.
19
+ #
20
+ # = Usage:
21
+ #
22
+ # require 'net/smtp'
23
+ # require 'mailit'
24
+ #
25
+ # mail = Mailit::Mail.new
26
+ # mail.to = 'test@test.com'
27
+ # mail.from = 'sender@sender.com'
28
+ # mail.subject 'Here are some files for you!'
29
+ # mail.text = 'This is what people with plain text mail readers will see'
30
+ # mail.html = "A little something <b>special</b> for people with HTML readers'
31
+ # mail.attach('/etc/fstab')
32
+ # mail.attach('/home/manveru/.vimrc')
33
+ #
34
+ # server = 'smtp1.testmailer.com'
35
+ # port = 25
36
+ # domain = 'mail.from.domain'
37
+ # password = 'foo'
38
+ #
39
+ # Net::SMTP.start(server, port, domain, mail.from, password, :cram_md5) do |smtp|
40
+ # smtp.send_message(mail.to_s, mail.from, mail.to)
41
+ # end
42
+ #
43
+ # = Todo:
44
+ #
45
+ # * MailFactory has a method_missing that handles getting and setting of
46
+ # arbitrary headers.
47
+ # I went for the less magical #[] and #[]= methods.
48
+ # Maybe someone can add the MailFactory behaviour.
49
+ class Mail
50
+ VERSION = '2009.03.02'
51
+
52
+ BOUNDARY_CHARS = [*'a'..'z'] + [*'A'..'Z'] + [*'0'..'9'] + ['.', '_']
53
+ BOUNDARY_PREFIX = "----=_NextPart_"
54
+
55
+ # body_boundary, encoding
56
+ BODY_BOUNDARY = "--%s\r\nContent-Type: %s\r\nContent-Transfer-Encoding: %s"
57
+
58
+ # attachment_boundary, mimetype, filename, filename
59
+ ATTACHMENT_BOUNDARY = "--%s\r\nContent-Type: %s; name=%p\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: inline; filename=%p"
60
+
61
+ HTML_BODY = <<BODY.strip
62
+ <html>
63
+ <head>
64
+ <meta http-equiv="Content-Type" content="text/html; charset=%s">
65
+ </head>
66
+ <body bgcolor="#ffffff" text="#000000">
67
+ %s
68
+ </body>
69
+ </html>
70
+ BODY
71
+
72
+ OPTIONS = {
73
+ :date => true,
74
+ :message_id => lambda{|mail|
75
+ time = Time.now
76
+ domain = mail['from'].first.to_s.split('@').last || 'localhost'
77
+ message_id = "<%f.%d.%d@%s>" % [time, $$, time.object_id, domain]
78
+ }
79
+ }
80
+
81
+
82
+ attr_accessor :charset, :text, :html, :attachment_boundary, :body_boundary
83
+ attr_reader :headers, :attachments
84
+
85
+ # Create an instance of {Mailit::Mailer}.
86
+ #
87
+ # @option options [String] :to
88
+ # @option options [String] :from
89
+ # @option options [String] :subject
90
+ # @option options [String] :text
91
+ # @option options [String] :html
92
+ # @author manveru
93
+ def initialize(options = {})
94
+ @headers = []
95
+ @attachments = []
96
+ @attachment_boundary = self.class.generate_boundary
97
+ @body_boundary = self.class.generate_boundary
98
+ @charset = 'utf-8'
99
+ @html = @text = nil
100
+
101
+ options.each{|key, value| __send__("#{key}=", value) }
102
+ end
103
+
104
+ def construct(options = {})
105
+ options = OPTIONS.merge(options)
106
+ time = Time.now
107
+
108
+ if message_id = options[:message_id]
109
+ self['Message-ID'] = message_id.call(self) unless self['Message-Id'].any?
110
+ end
111
+
112
+ if options[:date]
113
+ self['Date'] = time.rfc2822 unless self['Date'].any?
114
+ end
115
+
116
+ if multipart?
117
+ self['MIME-Version'] = '1.0' unless self['MIME-Version'].any?
118
+
119
+ unless self['Content-Type'].any?
120
+ if @attachments.any?
121
+ content_type = ('multipart/alternative; boundary=%p' % body_boundary)
122
+ else
123
+ content_type = ('multipart/mixed; boundary=%p' % attachment_boundary)
124
+ end
125
+
126
+ self['Content-Type'] = content_type
127
+ end
128
+ end
129
+
130
+ "#{header_string}#{body_string}"
131
+ end
132
+ alias to_s construct
133
+
134
+ ## Attachments
135
+
136
+ def add_attachment(filename, mimetype = nil, headers = nil)
137
+ container = {
138
+ :filename => Pathname.new(filename).basename,
139
+ :mimetype => (mimetype || mime_type_for(filename)),
140
+ }
141
+
142
+ add_attachment_common(container, filename, headers)
143
+ end
144
+ alias attach add_attachment
145
+
146
+ def add_attachment_as(file, filename, mimetype = nil, headers = nil)
147
+ container = {
148
+ :filename => filename,
149
+ :mimetype => (mimetype || mime_type_for(file))
150
+ }
151
+
152
+ add_attachment_common(container, file, headers)
153
+ end
154
+ alias attach_as add_attachment_as
155
+
156
+ ## Shortcuts
157
+
158
+ def multipart?
159
+ html || attachments.size > 0
160
+ end
161
+
162
+ def html=(html)
163
+ @html = HTML_BODY % [charset, html]
164
+ end
165
+
166
+ def raw_html=(html)
167
+ @html = html
168
+ end
169
+
170
+ def message_id=(id)
171
+ self['Message-ID'] = id
172
+ end
173
+
174
+ def send(options = {})
175
+ Mailer.send(self, options)
176
+ end
177
+
178
+ def defer_send(options = {})
179
+ Mailer.defer_send(self, options)
180
+ end
181
+
182
+ ## Header handling
183
+
184
+ def add_header(header, value)
185
+ case header.to_s.downcase
186
+ when /^subject$/i
187
+ value = quoted_printable_with_instruction(value)
188
+ when /^(from|to|bcc|reply-to)$/i
189
+ value = quote_address_if_necessary(value, charset)
190
+ end
191
+
192
+ @headers << [header, value]
193
+ end
194
+
195
+ def set_header(header, value)
196
+ remove_header(header)
197
+ add_header(header, value)
198
+ end
199
+ alias []= set_header
200
+
201
+ def remove_header(header)
202
+ regex = /^#{Regexp.escape(header)}/i
203
+
204
+ @headers.reject!{|key, value| key =~ regex }
205
+ end
206
+
207
+ def get_header(header)
208
+ regex = /^#{Regexp.escape(header)}/i
209
+
210
+ @headers.map{|key, value| value if regex =~ key }.compact
211
+ end
212
+ alias [] get_header
213
+
214
+ def header_string
215
+ headers.map{|key,value| "#{key}: #{value}"}.join("\r\n") << "\r\n\r\n"
216
+ end
217
+
218
+ MIME_INDICATOR = "This is a multi-part message in MIME format.\r\n\r\n--%s\r\nContent-Type: multipart/alternative; boundary=%p"
219
+
220
+ def body_string
221
+ return text unless multipart?
222
+
223
+ body = [ MIME_INDICATOR % [attachment_boundary, body_boundary] ]
224
+ body << build_body_boundary("text/plain; charset=#{charset} format=flowed")
225
+ body << "\r\n\r\n" << quote_if_necessary(text, charset)
226
+
227
+ if html
228
+ body << build_body_boundary("text/html; charset=#{charset}")
229
+ body << "\r\n\r\n" << quote_if_necessary(html, charset)
230
+ end
231
+
232
+ body << "--#{body_boundary}--"
233
+
234
+ attachments.each do |attachment|
235
+ body << build_attachment_boundary(attachment)
236
+ body << "\r\n\r\n" << attachment[:attachment]
237
+ body << "\r\n--#{attachment_boundary}--"
238
+ end
239
+
240
+ body.join("\r\n\r\n")
241
+ end
242
+
243
+ private
244
+
245
+ def add_attachment_common(container, file, headers)
246
+ container[:attachment] = file_read(file)
247
+ container[:headers] = headers_prepare(headers)
248
+ self.attachments << container
249
+ end
250
+
251
+ def headers_prepare(headers)
252
+ case headers
253
+ when Array
254
+ container[:headers] = headers
255
+ else
256
+ container[:headers] = headers.split(/\r?\n/)
257
+ end
258
+ end
259
+
260
+ def quoted_printable_with_instruction(text)
261
+ text = encode_quoted_printable_rfc2047(text)
262
+ "=?#{charset}?Q?#{text}?="
263
+ end
264
+
265
+ def quote_if_necessary(text, charset, instruction = false)
266
+ return unless text
267
+
268
+ if text.respond_to?(:force_encoding)
269
+ text = text.dup.force_encoding(Encoding::ASCII_8BIT)
270
+ end
271
+
272
+ if instruction
273
+ quoted_printable_with_instruction(text)
274
+ else
275
+ encode_quoted_printable_rfc2045(text)
276
+ end
277
+ end
278
+
279
+ def quote_address_if_necessary(address, charset)
280
+ case address
281
+ when Array
282
+ address.map{|addr| quote_address_if_necessary(addr, charset) }
283
+ when /^(\S.*)\s+(<.*>)$/
284
+ phrase = $1.gsub(/^['"](.*)['"]$/, '\1')
285
+ address = $2
286
+
287
+ phrase = quote_if_necessary(phrase, charset, true)
288
+
289
+ "%p %s" % [phrase, address]
290
+ else
291
+ address
292
+ end
293
+ end
294
+
295
+ def encode_file(string)
296
+ [string].pack('m')
297
+ end
298
+
299
+ def file_read(file)
300
+ case file
301
+ when String, Pathname
302
+ File.open(file.to_s, 'rb') do |io|
303
+ encode_file(io.read)
304
+ end
305
+ else
306
+ encode_file(file.read)
307
+ end
308
+ end
309
+
310
+ def encode_quoted_printable_rfc2045(string)
311
+ [string].pack('M').gsub(/\n/, "\r\n").chomp.gsub(/=$/, '')
312
+ end
313
+
314
+ def encode_quoted_printable_rfc2047(string)
315
+ string.enum_for(:each_byte).map{|ord|
316
+ if ord < 128 and ord != 61 # 61 is ascii '='
317
+ ord.chr
318
+ else
319
+ '=%X' % ord
320
+ end
321
+ }.join('').chomp.
322
+ gsub(/=$/,'').gsub('?', '=3F').gsub('_', '=5F').gsub(/ /, '_')
323
+ end
324
+
325
+ def build_body_boundary(type, encoding = 'quoted-printable')
326
+ BODY_BOUNDARY % [ body_boundary, type, encoding ]
327
+ end
328
+
329
+ def build_attachment_boundary(attachment)
330
+ mime, file, headers = attachment.values_at(:mimetype, :filename, :headers)
331
+
332
+ boundary = ATTACHMENT_BOUNDARY % [attachment_boundary, mime, file, file]
333
+ boundary << "\r\n%s" % headers.join("\r\n") if headers
334
+
335
+ boundary
336
+ end
337
+
338
+ # Try to get the
339
+ def mime_type_for(filename, override = nil)
340
+ override || Mime.type_for(filename)
341
+ end
342
+
343
+ ## Header shortcuts
344
+
345
+ def self.header_accessors(hash)
346
+ hash.each{|k,v| header_accessor(k, v) }
347
+ end
348
+
349
+ def self.header_accessor(method, header)
350
+ public
351
+ eval("def %s; self[%p].first; end" % [method, header.to_s])
352
+ eval("def %s=(o); self[%p] = o; end" % [method, header.to_s])
353
+ end
354
+
355
+ header_accessors(:reply_to => 'Reply-To', :to => :to, :from => :from,
356
+ :subject => :subject, :bcc => :bcc)
357
+
358
+ def self.generate_boundary(size = 25, chars = BOUNDARY_CHARS)
359
+ char_count = chars.size
360
+ postfix = Array.new(size){
361
+ chars[rand(char_count)]
362
+ }.join
363
+
364
+ return "#{BOUNDARY_PREFIX}#{postfix}"
365
+ end
366
+ end
367
+ end