mail_builder19 1.0-java

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.
@@ -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