mail_builder19 1.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ Doubleshot.new do |config|
4
+ config.project = "mail_builder19"
5
+ config.version = "1.0"
6
+
7
+ config.gem "mime-types"
8
+
9
+ config.gemspec do |spec|
10
+ spec.summary = "MailBuilder is a simple library for building RFC compliant MIME emails."
11
+ spec.description = <<-DESCRIPTION
12
+ MailBuilder is a simple library for building RFC compliant MIME emails.
13
+ DESCRIPTION
14
+ spec.homepage = "https://github.com/sam/mail_builder"
15
+ spec.authors = [ "Bernerd Schaefer", "Sam Smoot" ]
16
+ spec.email = "ssmoot@gmail.com"
17
+ spec.license = "MIT-LICENSE"
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Bernerd J. Schaefer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,56 @@
1
+ # MailBuilder
2
+
3
+ [![Build Status](https://secure.travis-ci.org/sam/mail_builder.png)](http://travis-ci.org/sam/mail_builder)
4
+
5
+ MailBuilder is a library for building RFC compliant MIME messages,
6
+ with support for text and HTML emails, as well as attachments.
7
+
8
+ ## Basic Usage
9
+
10
+ ```ruby
11
+ require 'rubygems'
12
+ require 'mail_builder'
13
+
14
+ mail = MailBuilder.new
15
+ mail.to = "joe@example.com"
16
+ mail.text = "Body"
17
+
18
+ sendmail = IO.popen("#{`which sendmail`.chomp} -i -t", "w+")
19
+ sendmail.puts mail
20
+ sendmail.close
21
+ ```
22
+
23
+ #### or
24
+
25
+ ```ruby
26
+ require 'net/smtp'
27
+
28
+ Net::SMTP.start("smtp.address.com", 25) do |smtp|
29
+ smtp.send_message(mail.to_s, mail.from, mail.to)
30
+ end
31
+ ```
32
+
33
+ ## Advantages
34
+
35
+ One of the key advantages to using MailBuilder over other libraries
36
+ (such as MailFactory) is that costly operations - generating boundaries,
37
+ reading attached files, etc. - are delayed until the physical message
38
+ is built (by calling `build` or `to_s`).
39
+
40
+ This gives you the freedom to generate many thousands of emails
41
+ with almost no time-cost. The emails could then be passed to some
42
+ external system - DRb, database - for final delivery.
43
+
44
+ ```ruby
45
+ 10_000.times do
46
+ mail = MailBuilder.new('to' => 'joe@example.com')
47
+ mail.html = View.new("mailers/large_mailer")
48
+ mail.attach("large_file.pdf")
49
+
50
+ ExternalMailServer.send!(mail)
51
+ end
52
+ ```
53
+
54
+ The external process would then build the email, at which point the
55
+ view would be rendered and attachments read, while the local process
56
+ would complete in very little (on my machine, sub-second) time.
@@ -0,0 +1,317 @@
1
+ # encoding: UTF-8
2
+
3
+ require "java"
4
+ require "pathname"
5
+ require "time"
6
+
7
+ require "rubygems"
8
+ require "mime/types"
9
+
10
+ ##
11
+ # MailBuilder is a library for building RFC compliant MIME messages,
12
+ # with support for text and HTML emails, as well as attachments.
13
+ #
14
+ # Basic Usage:
15
+ #
16
+ # mail = MailBuilder.new
17
+ # mail.to = "joe@example.com"
18
+ # mail.text = "Body"
19
+ #
20
+ # sendmail = IO.popen("#{`which sendmail`.chomp} -i -t", "w+")
21
+ # sendmail.puts mail
22
+ # sendmail.close
23
+ #
24
+ # # or
25
+ #
26
+ # require 'net/smtp'
27
+ #
28
+ # Net::SMTP.start("smtp.address.com", 25) do |smtp|
29
+ # smtp.send_message(mail.to_s, mail.from, mail.to)
30
+ # end
31
+ #
32
+ ##
33
+ class MailBuilder
34
+ require Pathname(__FILE__).dirname + "mail_builder/attachment"
35
+
36
+ ##
37
+ # Boundary characters, slightly adapted from those allowed by rfc1341,
38
+ # representing:
39
+ #
40
+ # ALPHA / DIGIT / "'" / "(" / ")" / "*" / "," / "-" / "." / "/" / ":"
41
+ #
42
+ # See 7.2.1, http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
43
+ ##
44
+ BOUNDARY_CHARS = ((39..58).to_a + (65..90).to_a + (97..122).to_a).map { |_| _.chr }.freeze
45
+
46
+ CHARSET = 'utf-8'.freeze
47
+
48
+ ##
49
+ # Printable characters which RFC 2047 says must be escaped.
50
+ ##
51
+ RFC2047_REPLACEMENTS = [
52
+ ['?', '=%X' % ??.ord],
53
+ ['_', '=%X' % ?_.ord],
54
+ [' ', '_'],
55
+ [/=$/, '']
56
+ ].freeze
57
+
58
+ attr_accessor :html, :text
59
+ attr_reader :headers, :attachments
60
+
61
+ ##
62
+ # Accepts an options hash, setting text and html if provided, and
63
+ # setting any provided headers.
64
+ #
65
+ # mailer = MailBuilder.new(:text => "Text", :to => "admin@site.com", "X-Priority" => 1)
66
+ # mailer.text # => "Text"
67
+ # mailer.to # => "admin@site.com"
68
+ # mailer.get_header("X-Priority") # => 1
69
+ ##
70
+ def initialize(options = {})
71
+ @headers = []
72
+ @attachments = []
73
+ @text = nil
74
+ @html = nil
75
+
76
+ parse_options(options)
77
+ end
78
+
79
+ ##
80
+ # Adds a header value to the mailer's headers, without removing
81
+ # previous values.
82
+ #
83
+ # mailer.add_header("From", "admin@example.com")
84
+ # mailer.add_header("From", "john@example.com")
85
+ #
86
+ # mailer.headers # => [["From", "admin@example.com"], ["From", "admin@example.com"]]
87
+ ##
88
+ def add_header(key, value)
89
+ @headers << [key.to_s, value]
90
+ end
91
+
92
+ ##
93
+ # Retrieves a value from the mailer's headers.
94
+ #
95
+ # mailer.get_header("to") # => "admin@example.com"
96
+ ##
97
+ def get_header(key)
98
+ @headers.detect { |k, v| return v if k == key }
99
+ end
100
+ alias [] get_header
101
+
102
+ def remove_header(key)
103
+ @headers.reject! { |k,| k == key }
104
+ end
105
+
106
+ ##
107
+ # Adds a header value to the mailer's headers, replacing previous values.
108
+ #
109
+ # mailer.add_header("From", "admin@example.com")
110
+ # mailer.set_header("From", "john@example.com")
111
+ #
112
+ # mailer.headers # => [["From", "admin@example.com"]]
113
+ ##
114
+ def set_header(key, value)
115
+ remove_header(key)
116
+ add_header(key, value)
117
+ end
118
+ alias []= set_header
119
+
120
+ ##
121
+ # Returns the envelope id for this mailer. The mail spec's ENV_ID is
122
+ # used to provide a unique identifier that follows an email through it's
123
+ # various states -- including bounces -- allowing it to be tracked.
124
+ ##
125
+ def envelope_id
126
+ @envelope_id ||= java.util.UUID.random_uuid.to_s
127
+ end
128
+
129
+ ##
130
+ # We define getters and setters for commonly used headers.
131
+ ##
132
+ %w(from to cc bcc reply_to subject).each do |header|
133
+ define_method(header) do
134
+ get_header(header)
135
+ end
136
+
137
+ define_method(header + "=") do |value|
138
+ set_header(header, value)
139
+ end
140
+ end
141
+
142
+ ##
143
+ # Wrapper for attach_as, setting the attachment filename to the file's
144
+ # basename.
145
+ #
146
+ # mailer.attach "some/file.pdf"
147
+ ##
148
+ def attach(file, type = nil, headers = nil)
149
+ file = Pathname(file)
150
+
151
+ attach_as(file, file.basename, type, headers)
152
+ end
153
+
154
+ ##
155
+ # Adds an Attachment to the email.
156
+ #
157
+ # If file is an IO or File object, it's contents will be read immediately, in case
158
+ # the mail will be delivered to an external service without access to the object.
159
+ # Otherwise, the attached file's content will be read when the message is built.
160
+ #
161
+ # If no type is provided, the MIME::Types library will be used to find a suitable
162
+ # content type.
163
+ #
164
+ # mailer.attach "account.html"
165
+ # mailer.attach_as StringIO.new("test"), "account.txt"
166
+ #
167
+ ##
168
+ def attach_as(file, name, type = nil, headers = nil)
169
+ @attachments << Attachment.new(file, name, type, headers)
170
+ end
171
+
172
+ def multipart?
173
+ attachments? || @html
174
+ end
175
+
176
+ def attachments?
177
+ @attachments.any?
178
+ end
179
+
180
+ ##
181
+ # Builds the full email message suitable to be passed to Sendmail, Net::SMTP, etc.
182
+ #
183
+ # It sets the Mail-From header (used for tracking emails), date, and message id,
184
+ # and then assembles the headers, body, and attachments.
185
+ #
186
+ # All expensive operations -- generating boundaries, rendering views, reading
187
+ # attached files -- are delayed until this method is called.
188
+ ##
189
+ def build
190
+ set_header("Mail-From", "#{ENV["USER"]}@localhost ENVID=#{envelope_id}")
191
+ set_header("Date", Time.now.rfc2822)
192
+ set_header("Message-ID", "<#{Time.now.to_f}.#{Process.pid}@#{get_header("from").to_s.split("@", 2)[1]}>")
193
+
194
+ if multipart?
195
+ set_header("Mime-Version", "1.0")
196
+ if attachments?
197
+ set_header("Content-Type", "multipart/mixed; boundary=\"#{attachment_boundary}\"")
198
+ else
199
+ set_header("Content-Type", "multipart/alternative; boundary=\"#{body_boundary}\"")
200
+ end
201
+ end
202
+
203
+ build_headers + build_body
204
+ end
205
+ alias to_s build
206
+
207
+ private
208
+
209
+ def build_headers
210
+ @headers.map do |header, value|
211
+ header = header.gsub("_", "-")
212
+ key = header.downcase
213
+
214
+ value = quote(value, 'rfc2047') if key == "subject"
215
+ value = quote_address(value) if %w(from to cc bcc reply-to).include?(key)
216
+
217
+ "#{header}: #{value}"
218
+ end.join("\r\n") + "\r\n\r\n"
219
+ end
220
+
221
+ def build_body
222
+ return @text.to_s unless multipart?
223
+
224
+ body = []
225
+ body << "This is a multi-part message in MIME format."
226
+ body << "--#{attachment_boundary}\r\nContent-Type: multipart/alternative; boundary=\"#{body_boundary}\""
227
+
228
+ body << build_body_boundary('text/plain')
229
+ body << quote(@text)
230
+
231
+ body << build_body_boundary('text/html')
232
+ body << quote(@html)
233
+
234
+ body << "--#{body_boundary}--"
235
+
236
+ if attachments?
237
+ @attachments.each do |attachment|
238
+ body << build_attachment_boundary(attachment)
239
+ body << attachment
240
+ body << "\r\n--#{attachment_boundary}--"
241
+ end
242
+ end
243
+
244
+ body.join("\r\n\r\n")
245
+ end
246
+
247
+ def build_body_boundary(type)
248
+ boundary = []
249
+ boundary << "--#{body_boundary}"
250
+ boundary << "Content-Type: #{type}; charset=#{CHARSET}#{'; format=flowed' if type == 'text/plain'}"
251
+ boundary << "Content-Transfer-Encoding: quoted-printable"
252
+ boundary.join("\r\n")
253
+ end
254
+
255
+ def build_attachment_boundary(attachment)
256
+ boundary = []
257
+ boundary << "--#{attachment_boundary}"
258
+ boundary << "Content-Type: #{attachment.type}; name=\"#{attachment.name}\""
259
+ boundary << "Content-Transfer-Encoding: base64"
260
+ boundary << "Content-Disposition: inline; filename=\"#{attachment.name}\""
261
+
262
+ boundary.push(*attachment.headers) if attachment.headers
263
+
264
+ boundary.join("\r\n")
265
+ end
266
+
267
+ def generate_boundary
268
+ "----=_NextPart_" + (1..25).map { BOUNDARY_CHARS[rand(BOUNDARY_CHARS.size)] }.join
269
+ end
270
+
271
+ def attachment_boundary
272
+ @attachment_boundary ||= generate_boundary
273
+ end
274
+
275
+ def body_boundary
276
+ @body_boundary ||= generate_boundary
277
+ end
278
+
279
+ def parse_options(options)
280
+ options.each do |key, value|
281
+ case key
282
+ when :html
283
+ self.html = value
284
+ when :text
285
+ self.text = text
286
+ else
287
+ set_header(key, value)
288
+ end
289
+ end
290
+ end
291
+
292
+ def quote(text, method = 'rfc2045')
293
+ return unless text
294
+
295
+ self.send("#{method}_encode", text)
296
+ end
297
+
298
+ def quote_address(address)
299
+ return address.map { |a| quote_address(a) }.join(", ") if address.is_a?(Array)
300
+
301
+ address.gsub(/['"](.+?)['"]\s+(<.+?>)/) do
302
+ "\"#{quote($1, 'rfc2047')}\" #{$2}"
303
+ end
304
+ end
305
+
306
+ def rfc2047_encode(text)
307
+ text = text.enum_for(:each_byte).map { |ord| ord < 128 && ord != ?=.ord ? ord.chr : "=%X" % ord }.join.chomp
308
+
309
+ RFC2047_REPLACEMENTS.each { |replacement| text.gsub!(*replacement) }
310
+
311
+ "=?#{CHARSET}?Q?#{text}?="
312
+ end
313
+
314
+ def rfc2045_encode(text)
315
+ [text].pack('M').gsub("\n", "\r\n").chomp("=\r\n")
316
+ end
317
+ end
@@ -0,0 +1,35 @@
1
+ class MailBuilder
2
+ class Attachment
3
+
4
+ attr_accessor :file, :name, :type, :headers
5
+
6
+ def initialize(file, name, type, headers)
7
+ @file = file
8
+ @name = name
9
+ @type = type
10
+ @headers = headers
11
+
12
+ # If we have a File/IO object, read it. Otherwise, we'll read it lazily.
13
+ @body = file.read() if !file.kind_of?(Pathname) && file.respond_to?(:read)
14
+ end
15
+
16
+ def to_s
17
+ @body ||= File.open(file.to_s(), "rb") { |f| f.read() }
18
+
19
+ [@body].pack("m")
20
+ end
21
+
22
+ def name
23
+ @name
24
+ end
25
+
26
+ def type
27
+ @type ||= MIME::Types.type_for(@name.to_s).to_s
28
+ end
29
+
30
+ def inspect
31
+ "#<MailBuilder::Attachment @file=#{@file.inspect}> @name=#{@name.inspect} @type=#{@type.inspect} @headers=#{@headers.inspect}"
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ require "doubleshot/setup"
2
+
3
+ require "minitest/autorun"
4
+ require "minitest/pride"
5
+ require "minitest/wscolor"
6
+
7
+ require Pathname(__FILE__).dirname.parent + "lib/mail_builder"
8
+
9
+ (MailBuilder.private_instance_methods - Object.private_instance_methods).each do |method|
10
+ MailBuilder.send(:public, method)
11
+ end
12
+
13
+ def mailer(options = {})
14
+ MailBuilder.new(options)
15
+ end
16
+
17
+ module MiniTest
18
+ module Assertions
19
+ def assert_nothing_raised *args
20
+ yield
21
+ assert true, "Nothing raised"
22
+ rescue Exception => e
23
+ fail "Expected nothing raised, but got #{e.class}: #{e.message}"
24
+ end
25
+ end
26
+
27
+ module Expectations
28
+ infect_an_assertion :assert_nothing_raised, :wont_raise
29
+ end
30
+ end
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env jruby
2
+
3
+ # encoding: UTF-8
4
+
5
+ require File.dirname(__FILE__) + '/helper'
6
+
7
+ describe MailBuilder do
8
+
9
+ it "must encode recipients properly" do
10
+ mailer(:to => "bernerd@wieck.com").build_headers.strip
11
+ .must_equal "to: bernerd@wieck.com"
12
+
13
+ mailer(:to => '"Bernerd" <bernerd@wieck.com>').build_headers.strip
14
+ .must_equal "to: \"=?utf-8?Q?Bernerd?=\" <bernerd@wieck.com>"
15
+ end
16
+
17
+ it "must encode multiple recipients properly" do
18
+ desired_to = 'to: "=?utf-8?Q?Bernerd?=" <bernerd@wieck.com>, "=?utf-8?Q?Adam?=" <adam@wieck.com>'
19
+
20
+ mailer(:to => '"Bernerd" <bernerd@wieck.com>, "Adam" <adam@wieck.com>')
21
+ .build_headers.strip.must_equal desired_to
22
+ mailer(:to => ['"Bernerd" <bernerd@wieck.com>', '"Adam" <adam@wieck.com>'])
23
+ .build_headers.strip.must_equal desired_to
24
+ end
25
+
26
+ it "must encode all addresses" do
27
+ [ "from", "to", "cc", "bcc", "reply-to" ].each do |field|
28
+ mailer(field => '"Bernerd" <bernerd@wieck.com>').build_headers.strip
29
+ .must_equal "#{field}: \"=?utf-8?Q?Bernerd?=\" <bernerd@wieck.com>"
30
+ end
31
+ end
32
+
33
+ it "must rfc2045 encode properly" do
34
+ mailer.rfc2045_encode('<a href="http://google.com">Google</a>')
35
+ .must_equal '<a href=3D"http://google.com">Google</a>'
36
+ end
37
+
38
+ it "must rfc2047 encode properly" do
39
+ mailer.rfc2047_encode("This + is = a _ test * subject")
40
+ .must_equal "=?utf-8?Q?This_+_is_=3D_a_=5F_test_*_subject?="
41
+
42
+ mailer.rfc2047_encode("\303\244\303\244\303\244\303\266\303\266\303\266")
43
+ .must_equal "=?utf-8?Q?=C3=A4=C3=A4=C3=A4=C3=B6=C3=B6=C3=B6?="
44
+ end
45
+
46
+ it "mailer must not error on build if empty" do
47
+ -> { mailer.to_s }.wont_raise
48
+ end
49
+
50
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mail_builder19
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ prerelease:
6
+ platform: java
7
+ authors:
8
+ - Bernerd Schaefer
9
+ - Sam Smoot
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-12-05 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mime-types
17
+ version_requirements: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: !binary |-
22
+ MA==
23
+ none: false
24
+ requirement: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ! '>='
27
+ - !ruby/object:Gem::Version
28
+ version: !binary |-
29
+ MA==
30
+ none: false
31
+ prerelease: false
32
+ type: :runtime
33
+ - !ruby/object:Gem::Dependency
34
+ name: doubleshot
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: !binary |-
40
+ MA==
41
+ none: false
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: !binary |-
47
+ MA==
48
+ none: false
49
+ prerelease: false
50
+ type: :development
51
+ description: |
52
+ MailBuilder is a simple library for building RFC compliant MIME emails.
53
+ email: ssmoot@gmail.com
54
+ executables: []
55
+ extensions: []
56
+ extra_rdoc_files: []
57
+ files:
58
+ - test/helper.rb
59
+ - test/mail_builder_spec.rb
60
+ - Doubleshot
61
+ - MIT-LICENSE
62
+ - README.md
63
+ - lib/mail_builder/attachment.rb
64
+ - lib/mail_builder.rb
65
+ homepage: https://github.com/sam/mail_builder
66
+ licenses:
67
+ - MIT-LICENSE
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --line-numbers
71
+ - --main
72
+ - README.textile
73
+ - --title
74
+ - mail_builder19 Documentation
75
+ - lib
76
+ - README.textile
77
+ require_paths:
78
+ - lib
79
+ - target
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: !binary |-
85
+ MA==
86
+ none: false
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: !binary |-
92
+ MA==
93
+ none: false
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 1.8.24
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: MailBuilder is a simple library for building RFC compliant MIME emails.
100
+ test_files:
101
+ - test/helper.rb
102
+ - test/mail_builder_spec.rb