From 359caef33ca71b37b3ea2feef0960beccfabf4de Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Sun, 29 May 2005 16:36:22 +0000 Subject: A very thorough refactoring, resulting in new mail property setters and support for attachments and multipart messages. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1359 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionmailer/CHANGELOG | 6 +- actionmailer/lib/action_mailer.rb | 4 +- .../lib/action_mailer/adv_attr_accessor.rb | 58 ++++ actionmailer/lib/action_mailer/base.rb | 323 +++++++++++++-------- actionmailer/lib/action_mailer/part.rb | 84 ++++++ actionmailer/lib/action_mailer/quoting.rb | 102 +++++++ .../lib/action_mailer/vendor/tmail/encode.rb | 1 + .../lib/action_mailer/vendor/tmail/facade.rb | 3 +- .../lib/action_mailer/vendor/tmail/utils.rb | 2 +- .../implicitly_multipart_example.text.html.rhtml | 10 + .../implicitly_multipart_example.text.plain.rhtml | 2 + actionmailer/test/mail_service_test.rb | 96 ++++-- 12 files changed, 539 insertions(+), 152 deletions(-) create mode 100644 actionmailer/lib/action_mailer/adv_attr_accessor.rb create mode 100644 actionmailer/lib/action_mailer/part.rb create mode 100644 actionmailer/lib/action_mailer/quoting.rb create mode 100644 actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.rhtml create mode 100644 actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.plain.rhtml diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index b49821bcd5..3a7a511916 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -1,5 +1,9 @@ *SVN* +* Support attachments and multipart messages. + +* Added new accessors for the various mail properties. + * Fix to only perform the charset conversion if a 'from' and a 'to' charset are given (make no assumptions about what the charset was) #1276 [Jamis Buck] * Fix attachments and content-type problems #1276 [Jamis Buck] @@ -117,4 +121,4 @@ *0.3* -* First release \ No newline at end of file +* First release diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 3b507f63d1..bc0a7a6ec4 100755 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -36,11 +36,13 @@ $:.unshift(File.dirname(__FILE__) + "/action_mailer/vendor/") require 'action_mailer/base' require 'action_mailer/mail_helper' +require 'action_mailer/quoting' require 'action_mailer/vendor/tmail' require 'net/smtp' ActionView::Base.class_eval { include MailHelper } +ActionMailer::Base.class_eval { include ActionMailer::Quoting } old_verbose, $VERBOSE = $VERBOSE, nil TMail::Encoder.const_set("MAX_LINE_LEN", 200) -$VERBOSE = old_verbose \ No newline at end of file +$VERBOSE = old_verbose diff --git a/actionmailer/lib/action_mailer/adv_attr_accessor.rb b/actionmailer/lib/action_mailer/adv_attr_accessor.rb new file mode 100644 index 0000000000..47c02ba198 --- /dev/null +++ b/actionmailer/lib/action_mailer/adv_attr_accessor.rb @@ -0,0 +1,58 @@ +module ActionMailer + module AdvAttrAccessor + def self.append_features(base) + super + base.extend(ClassMethods) + end + + module ClassMethods + + def adv_attr_accessor(*names) + names.each do |name| + define_method("#{name}=") do |value| + instance_variable_set("@#{name}", value) + end + + define_method(name) do |*parameters| + raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1 + if parameters.empty? + instance_variable_get("@#{name}") + else + instance_variable_set("@#{name}", parameters.first) + end + end + end + end + + end + end +end +module ActionMailer + module AdvAttrAccessor + def self.append_features(base) + super + base.extend(ClassMethods) + end + + module ClassMethods + + def adv_attr_accessor(*names) + names.each do |name| + define_method("#{name}=") do |value| + instance_variable_set("@#{name}", value) + end + + define_method(name) do |*parameters| + raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1 + if parameters.empty? + instance_variable_get("@#{name}") + else + instance_variable_set("@#{name}", parameters.first) + end + end + end + end + + end + end +end diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 9afe5138d9..15f08caf40 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,23 +1,70 @@ +require 'action_mailer/adv_attr_accessor' +require 'action_mailer/part' + module ActionMailer #:nodoc: # Usage: # # class ApplicationMailer < ActionMailer::Base - # def post_notification(recipients, post) - # @recipients = recipients - # @from = post.author.email_address_with_name - # @headers["bcc"] = SYSTEM_ADMINISTRATOR_EMAIL - # @headers["reply-to"] = "notifications@example.com" - # @subject = "[#{post.account.name} #{post.title}]" - # @body["post"] = post + # # Set up properties + # # (Properties can also be specified via accessor methods + # # i.e. self.subject = "foo") and instance variables (@subject = "foo"). + # def signup_notification(recipient) + # recipients recipient.email_address_with_name + # subject "New account information" + # body Hash.new("account" => recipient) + # from "system@example.com" + # end + # + # # explicitly specify multipart messages + # def signup_notification(recipient) + # recipients recipient.email_address_with_name + # subject "New account information" + # from "system@example.com" + # + # part :content_type => "text/html", + # :body => render_message("signup-as-html", :account => recipient) + # + # part "text/plain" do |p| + # p.body = render_message("signup-as-plain", :account => recipient) + # p.transfer_encoding = "base64" + # end + # end + # + # # attachments + # def signup_notification(recipient) + # recipients recipient.email_address_with_name + # subject "New account information" + # from "system@example.com" + # + # attachment :content_type => "image/jpeg", + # :body => File.read("an-image.jpg") + # + # attachment "application/pdf" do |a| + # a.body = generate_your_pdf_here() + # end # end - # - # def comment_notification(recipient, comment) - # @recipients = recipient.email_address_with_name - # @subject = "[#{comment.post.project.client.firm.account.name}]" + - # " Re: #{comment.post.title}" - # @body["comment"] = comment - # @from = comment.author.email_address_with_name - # @sent_on = comment.posted_on + # + # # implicitly multipart messages + # def signup_notification(recipient) + # recipients recipient.email_address_with_name + # subject "New account information" + # from "system@example.com" + # body(:account => "recipient") + # + # # ActionMailer will automatically detect and use multipart templates, + # # where each template is named after the name of the action, followed + # # by the content type. Each such detected template will be added as + # # a separate part to the message. + # # + # # for example, if the following templates existed: + # # * signup_notification.text.plain.rhtml + # # * signup_notification.text.html.rhtml + # # * signup_notification.text.xml.rxml + # # * signup_notification.text.x-yaml.rhtml + # # + # # Each would be rendered and added as a separate part to the message, + # # with the corresponding content type. The same body hash is passed to + # # each template. # end # end # @@ -57,8 +104,10 @@ module ActionMailer #:nodoc: # for unit and functional testing. # # * default_charset - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also - # pick a different charset from inside a method with @encoding. + # pick a different charset from inside a method with @charset. class Base + include ActionMailer::AdvAttrAccessor + private_class_method :new #:nodoc: cattr_accessor :template_root @@ -89,94 +138,167 @@ module ActionMailer #:nodoc: @@default_charset = "utf-8" cattr_accessor :default_charset - attr_accessor :recipients, :subject, :body, :from, :sent_on, :headers, :bcc, :cc, :charset + adv_attr_accessor :recipients, :subject, :body, :from, :sent_on, :headers, + :bcc, :cc, :charset - def initialize - @bcc = @cc = @from = @recipients = @sent_on = @subject = @body = nil + attr_reader :mail + + # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer + # will be initialized according to the named method. If not, the mailer will + # remain uninitialized (useful when you only need to invoke the "receive" + # method, for instance). + def initialize(method_name=nil, *parameters) + create!(method_name, *parameters) if method_name + end + + # Initialize the mailer via the given +method_name+. The body will be + # rendered and a new TMail::Mail object created. + def create!(method_name, *parameters) + @bcc = @cc = @from = @recipients = @sent_on = @subject = nil @charset = @@default_charset.dup + @parts = [] @headers = {} - end + @body = {} + + send(method_name, *parameters) + + # If an explicit, textual body has not been set, we check assumptions. + unless String === @body + # First, we look to see if there are any likely templates that match, + # which include the content-type in their file name (i.e., + # "the_template_file.text.html.rhtml", etc.). + if @parts.empty? + templates = Dir.glob("#{template_path}/#{method_name}.*") + templates.each do |path| + type = (File.basename(path).split(".")[1..-2] || []).join("/") + next if type.empty? + @parts << Part.new(:content_type => type, + :disposition => "inline", :charset => "charset", + :body => render_message(File.basename(path).split(".")[0..-2].join('.'), @body)) + end + end - class << self - def method_missing(method_symbol, *parameters)#:nodoc: - case method_symbol.id2name - when /^create_([_a-z]\w*)/ then create_from_action($1, *parameters) - when /^deliver_([_a-z]\w*)/ then deliver(send("create_" + $1, *parameters)) + # Then, if there were such templates, we check to see if we ought to + # also render a "normal" template (without the content type). If a + # normal template exists (or if there were no implicit parts) we render + # it. + template_exists = @parts.empty? + template_exists ||= Dir.glob("#{template_path}/#{method_name}.*").any? { |i| i.split(".").length == 2 } + @body = render_message(method_name, @body) if template_exists + + # Finally, if there are other message parts and a textual body exists, + # we shift it onto the front of the parts and set the body to nil (so + # that create_mail doesn't try to render it in addition to the parts). + if !@parts.empty? && String === @body + @parts.unshift Part.new(:charset => charset, :body => @body) + @body = nil end end - def mail(to, subject, body, from, timestamp = nil, headers = {}, charset = @@default_charset) #:nodoc: - deliver(create(to, subject, body, from, timestamp, headers, charset)) - end + # build the mail object itself + @mail = create_mail + end - def create(to, subject, body, from, timestamp = nil, headers = {}, charset = @@default_charset) #:nodoc: - m = TMail::Mail.new - m.body = body - m.subject, = quote_any_if_necessary(charset, subject) - m.to, m.from = quote_any_address_if_necessary(charset, to, from) + # Delivers the cached TMail::Mail object. If no TMail::Mail object has been + # created (via the #create! method, for instance) this will fail. + def deliver! + raise "no mail object available for delivery!" unless @mail + logger.info "Sent mail:\n #{mail.encoded}" unless logger.nil? - m.date = timestamp.respond_to?("to_time") ? timestamp.to_time : (timestamp || Time.now) + begin + send("perform_delivery_#{delivery_method}", @mail) if perform_deliveries + rescue Object => e + raise e if raise_delivery_errors + end - m.set_content_type "text", "plain", { "charset" => charset } + return @mail + end - headers.each do |k, v| - m[k] = v - end + # Add a part to a multipart message, with the given content-type. The + # part itself is yielded to the block, so that other properties (charset, + # body, headers, etc.) can be set on it. + def part(params) + params = {:content_type => params} if String === params + part = Part.new(params) + yield part if block_given? + @parts << part + end + + # Add an attachment to a multipart message. This is simply a part with the + # content-disposition set to "attachment". + def attachment(params, &block) + params = { :content_type => params } if String === params + params = { :disposition => "attachment", + :transfer_encoding => "base64" }.merge(params) + part(params, &block) + end - return m + private + + def render_message(method_name, body) + ActionView::Base.new(template_path, body).render_file(method_name) + end + + def template_path + template_root + "/" + Inflector.underscore(self.class.name) end - def deliver(mail) #:nodoc: - logger.info "Sent mail:\n #{mail.encoded}" unless logger.nil? + def create_mail + m = TMail::Mail.new - begin - send("perform_delivery_#{delivery_method}", mail) if perform_deliveries - rescue Object => e - raise e if raise_delivery_errors - end + m.subject, = quote_any_if_necessary(charset, subject) + m.to, m.from = quote_any_address_if_necessary(charset, recipients, from) + m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil? + m.cc = quote_address_if_necessary(cc, charset) unless cc.nil? - return mail - end + m.date = sent_on.to_time rescue sent_on if sent_on + headers.each { |k, v| m[k] = v } - def quoted_printable(text, charset)#:nodoc: - text = text.gsub( /[^a-z ]/i ) { "=%02x" % $&[0] }.gsub( / /, "_" ) - "=?#{charset}?Q?#{text}?=" - end + if @parts.empty? + m.set_content_type "text", "plain", { "charset" => charset } + m.body = body + else + if String === body + part = TMail::Mail.new + part.body = body + part.set_content_type "text", "plain", { "charset" => charset } + part.set_content_disposition "inline" + m.parts << part + end - CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/ + @parts.each do |p| + part = (TMail::Mail === p ? p : p.to_mail(self)) + m.parts << part + end + end - # Quote the given text if it contains any "illegal" characters - def quote_if_necessary(text, charset) - (text =~ CHARS_NEEDING_QUOTING) ? - quoted_printable(text, charset) : - text + @mail = m end - # Quote any of the given strings if they contain any "illegal" characters - def quote_any_if_necessary(charset, *args) - args.map { |v| quote_if_necessary(v, charset) } + def perform_delivery_smtp(mail) + Net::SMTP.start(server_settings[:address], server_settings[:port], server_settings[:domain], + server_settings[:user_name], server_settings[:password], server_settings[:authentication]) do |smtp| + smtp.sendmail(mail.encoded, mail.from, mail.destinations) + end end - # Quote the given address if it needs to be. The address may be a - # regular email address, or it can be a phrase followed by an address in - # brackets. The phrase is the only part that will be quoted, and only if - # it needs to be. This allows extended characters to be used in the - # "to", "from", "cc", and "bcc" headers. - def quote_address_if_necessary(address, charset) - if Array === address - address.map { |a| quote_address_if_necessary(a, charset) } - elsif address =~ /^(\S.*)\s+(<.*>)$/ - address = $2 - phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset) - "\"#{phrase}\" #{address}" - else - address + def perform_delivery_sendmail(mail) + IO.popen("/usr/sbin/sendmail -i -t","w+") do |sm| + sm.print(mail.encoded) + sm.flush end end - # Quote any of the given addresses, if they need to be. - def quote_any_address_if_necessary(charset, *args) - args.map { |v| quote_address_if_necessary(v, charset) } + def perform_delivery_test(mail) + deliveries << mail + end + + class << self + def method_missing(method_symbol, *parameters)#:nodoc: + case method_symbol.id2name + when /^create_([_a-z]\w*)/ then new($1, *parameters).mail + when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver! + end end def receive(raw_email) @@ -186,51 +308,6 @@ module ActionMailer #:nodoc: new.receive(mail) end - private - def perform_delivery_smtp(mail) - Net::SMTP.start(server_settings[:address], server_settings[:port], server_settings[:domain], - server_settings[:user_name], server_settings[:password], server_settings[:authentication]) do |smtp| - smtp.sendmail(mail.encoded, mail.from, mail.destinations) - end - end - - def perform_delivery_sendmail(mail) - IO.popen("/usr/sbin/sendmail -i -t","w+") do |sm| - sm.print(mail.encoded) - sm.flush - end - end - - def perform_delivery_test(mail) - deliveries << mail - end - - def create_from_action(method_name, *parameters) - mailer = new - mailer.body = {} - mailer.send(method_name, *parameters) - - unless String === mailer.body then - mailer.body = render_body mailer, method_name - end - - mail = create(mailer.recipients, mailer.subject, mailer.body, - mailer.from, mailer.sent_on, mailer.headers, - mailer.charset) - - mail.bcc = quote_address_if_necessary(mailer.bcc, mailer.charset) unless mailer.bcc.nil? - mail.cc = quote_address_if_necessary(mailer.cc, mailer.charset) unless mailer.cc.nil? - - return mail - end - - def render_body(mailer, method_name) - ActionView::Base.new(template_path, mailer.body).render_file(method_name) - end - - def template_path - template_root + "/" + Inflector.underscore(self.to_s) - end end end end diff --git a/actionmailer/lib/action_mailer/part.rb b/actionmailer/lib/action_mailer/part.rb new file mode 100644 index 0000000000..e26742816a --- /dev/null +++ b/actionmailer/lib/action_mailer/part.rb @@ -0,0 +1,84 @@ +require 'action_mailer/adv_attr_accessor' + +module ActionMailer + + class Part #:nodoc: + include ActionMailer::AdvAttrAccessor + + adv_attr_accessor :content_type, :content_disposition, :charset, :body + adv_attr_accessor :filename, :transfer_encoding, :headers + + def initialize(params) + @content_type = params[:content_type] || "text/plain" + @content_disposition = params[:disposition] || "inline" + @charset = params[:charset] + @body = params[:body] + @filename = params[:filename] + @transfer_encoding = params[:transfer_encoding] || "quoted-printable" + @headers = params[:headers] || {} + end + + def to_mail(defaults) + part = TMail::Mail.new + part.set_content_type(content_type, nil, + "charset" => charset || defaults.charset, "name" => filename) + part.set_content_disposition(content_disposition, + "filename" => filename) + + part.content_transfer_encoding = transfer_encoding || "quoted-printable" + case (transfer_encoding || "").downcase + when "base64" then + part.body = TMail::Base64.encode(body) + when "quoted-printable" + part.body = [body].pack("M*") + else + part.body = body + end + + part + end + end + +end +require 'action_mailer/adv_attr_accessor' + +module ActionMailer + + class Part #:nodoc: + include ActionMailer::AdvAttrAccessor + + adv_attr_accessor :content_type, :content_disposition, :charset, :body + adv_attr_accessor :filename, :transfer_encoding, :headers + + def initialize(params) + @content_type = params[:content_type] || "text/plain" + @content_disposition = params[:disposition] || "inline" + @charset = params[:charset] + @body = params[:body] + @filename = params[:filename] + @transfer_encoding = params[:transfer_encoding] || "quoted-printable" + @headers = params[:headers] || {} + end + + def to_mail(defaults) + part = TMail::Mail.new + part.set_content_type(content_type, nil, + "charset" => charset || defaults.charset, "name" => filename) + part.set_content_disposition(content_disposition, + "filename" => filename) + + part.content_transfer_encoding = transfer_encoding || "quoted-printable" + case (transfer_encoding || "").downcase + when "base64" then + part.body = TMail::Base64.encode(body) + when "quoted-printable" + part.body = [body].pack("M*") + else + part.body = body + end + + part + end + end + +end diff --git a/actionmailer/lib/action_mailer/quoting.rb b/actionmailer/lib/action_mailer/quoting.rb new file mode 100644 index 0000000000..28c456f25d --- /dev/null +++ b/actionmailer/lib/action_mailer/quoting.rb @@ -0,0 +1,102 @@ +module ActionMailer + module Quoting + + # Convert the given text into quoted printable format, with an instruction + # that the text be eventually interpreted in the given charset. + def quoted_printable(text, charset) + text = text.gsub( /[^a-z ]/i ) { "=%02x" % $&[0] }.gsub( / /, "_" ) + "=?#{charset}?Q?#{text}?=" + end + + # A quick-and-dirty regexp for determining whether a string contains any + # characters that need escaping. + CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/ + + # Quote the given text if it contains any "illegal" characters + def quote_if_necessary(text, charset) + (text =~ CHARS_NEEDING_QUOTING) ? + quoted_printable(text, charset) : + text + end + + # Quote any of the given strings if they contain any "illegal" characters + def quote_any_if_necessary(charset, *args) + args.map { |v| quote_if_necessary(v, charset) } + end + + # Quote the given address if it needs to be. The address may be a + # regular email address, or it can be a phrase followed by an address in + # brackets. The phrase is the only part that will be quoted, and only if + # it needs to be. This allows extended characters to be used in the + # "to", "from", "cc", and "bcc" headers. + def quote_address_if_necessary(address, charset) + if Array === address + address.map { |a| quote_address_if_necessary(a, charset) } + elsif address =~ /^(\S.*)\s+(<.*>)$/ + address = $2 + phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset) + "\"#{phrase}\" #{address}" + else + address + end + end + + # Quote any of the given addresses, if they need to be. + def quote_any_address_if_necessary(charset, *args) + args.map { |v| quote_address_if_necessary(v, charset) } + end + + end +end +module ActionMailer + module Quoting + + # Convert the given text into quoted printable format, with an instruction + # that the text be eventually interpreted in the given charset. + def quoted_printable(text, charset) + text = text.gsub( /[^a-z ]/i ) { "=%02x" % $&[0] }.gsub( / /, "_" ) + "=?#{charset}?Q?#{text}?=" + end + + # A quick-and-dirty regexp for determining whether a string contains any + # characters that need escaping. + if !defined?(CHARS_NEEDING_QUOTING) + CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/ + end + + # Quote the given text if it contains any "illegal" characters + def quote_if_necessary(text, charset) + (text =~ CHARS_NEEDING_QUOTING) ? + quoted_printable(text, charset) : + text + end + + # Quote any of the given strings if they contain any "illegal" characters + def quote_any_if_necessary(charset, *args) + args.map { |v| quote_if_necessary(v, charset) } + end + + # Quote the given address if it needs to be. The address may be a + # regular email address, or it can be a phrase followed by an address in + # brackets. The phrase is the only part that will be quoted, and only if + # it needs to be. This allows extended characters to be used in the + # "to", "from", "cc", and "bcc" headers. + def quote_address_if_necessary(address, charset) + if Array === address + address.map { |a| quote_address_if_necessary(a, charset) } + elsif address =~ /^(\S.*)\s+(<.*>)$/ + address = $2 + phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset) + "\"#{phrase}\" #{address}" + else + address + end + end + + # Quote any of the given addresses, if they need to be. + def quote_any_address_if_necessary(charset, *args) + args.map { |v| quote_address_if_necessary(v, charset) } + end + + end +end diff --git a/actionmailer/lib/action_mailer/vendor/tmail/encode.rb b/actionmailer/lib/action_mailer/vendor/tmail/encode.rb index c0909f30d2..c7bbeef4c0 100755 --- a/actionmailer/lib/action_mailer/vendor/tmail/encode.rb +++ b/actionmailer/lib/action_mailer/vendor/tmail/encode.rb @@ -253,6 +253,7 @@ module TMail # FIXME: implement line folding # def kv_pair( k, v ) + return if v.nil? v = normalize_encoding(v) if token_safe?(v) add_text k + '=' + v diff --git a/actionmailer/lib/action_mailer/vendor/tmail/facade.rb b/actionmailer/lib/action_mailer/vendor/tmail/facade.rb index 0a12a61a75..cdbd4764f1 100755 --- a/actionmailer/lib/action_mailer/vendor/tmail/facade.rb +++ b/actionmailer/lib/action_mailer/vendor/tmail/facade.rb @@ -442,7 +442,8 @@ module TMail h.disposition = str h.params.clear else - h = store('Content-Disposition', str) + store('Content-Disposition', str) + h = @header['content-disposition'] end h.params.replace params if params end diff --git a/actionmailer/lib/action_mailer/vendor/tmail/utils.rb b/actionmailer/lib/action_mailer/vendor/tmail/utils.rb index 4f603b96b9..57df234464 100755 --- a/actionmailer/lib/action_mailer/vendor/tmail/utils.rb +++ b/actionmailer/lib/action_mailer/vendor/tmail/utils.rb @@ -27,7 +27,7 @@ module TMail t = Time.now sprintf('%x%x_%x%x%d%x', t.to_i, t.tv_usec, - $$, Thread.current.id, @uniq, rand(255)) + $$, Thread.current.object_id, @uniq, rand(255)) end private_class_method :random_tag diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.rhtml b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.rhtml new file mode 100644 index 0000000000..946d99ede5 --- /dev/null +++ b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.rhtml @@ -0,0 +1,10 @@ + + + HTML formatted message to <%= @recipient %>. + + + + + HTML formatted message to <%= @recipient %>. + + diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.plain.rhtml b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.plain.rhtml new file mode 100644 index 0000000000..a6c8d54cf9 --- /dev/null +++ b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.plain.rhtml @@ -0,0 +1,2 @@ +Plain text to <%= @recipient %>. +Plain text to <%= @recipient %>. diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index 733ec61e41..861206fc54 100755 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -14,21 +14,21 @@ class TestMailer < ActionMailer::Base end def cancelled_account(recipient) - @recipients = recipient - @subject = "[Cancelled] Goodbye #{recipient}" - @from = "system@loudthinking.com" - @sent_on = Time.local(2004, 12, 12) - @body = "Goodbye, Mr. #{recipient}" + self.recipients = recipient + self.subject = "[Cancelled] Goodbye #{recipient}" + self.from = "system@loudthinking.com" + self.sent_on = Time.local(2004, 12, 12) + self.body = "Goodbye, Mr. #{recipient}" end def cc_bcc(recipient) - @recipients = recipient - @subject = "testing bcc/cc" - @from = "system@loudthinking.com" - @sent_on = Time.local 2004, 12, 12 - @cc = "nobody@loudthinking.com" - @bcc = "root@loudthinking.com" - @body = "Nothing to see here." + recipients recipient + subject "testing bcc/cc" + from "system@loudthinking.com" + sent_on Time.local(2004, 12, 12) + cc "nobody@loudthinking.com" + bcc "root@loudthinking.com" + body "Nothing to see here." end def iso_charset(recipient) @@ -74,6 +74,30 @@ class TestMailer < ActionMailer::Base @charset = "utf-8" end + def explicitly_multipart_example(recipient) + @recipients = recipient + @subject = "multipart example" + @from = "test@example.com" + @sent_on = Time.local 2004, 12, 12 + @body = "plain text default" + + part "text/html" do |p| + p.charset = "iso-8859-1" + p.body = "blah" + end + + attachment :content_type => "image/jpeg", :filename => "foo.jpg", + :body => "123456789" + end + + def implicitly_multipart_example(recipient) + @recipients = recipient + @subject = "multipart example" + @from = "test@example.com" + @sent_on = Time.local 2004, 12, 12 + @body = { "recipient" => recipient } + end + class <", "iso-8859-1" - expected.cc = TestMailer.quote_address_if_necessary "Grytøyr ", "iso-8859-1" - expected.bcc = TestMailer.quote_address_if_necessary "Grytøyr ", "iso-8859-1" + expected.from = quote_address_if_necessary "Grytøyr ", "iso-8859-1" + expected.cc = quote_address_if_necessary "Grytøyr ", "iso-8859-1" + expected.bcc = quote_address_if_necessary "Grytøyr ", "iso-8859-1" expected.date = Time.local 2004, 12, 12 created = nil @@ -339,12 +364,12 @@ EOF def test_utf8_body_is_not_quoted @recipient = "Foo áëô îü " expected = new_mail "utf-8" - expected.to = TestMailer.quote_address_if_necessary @recipient, "utf-8" + expected.to = quote_address_if_necessary @recipient, "utf-8" expected.subject = "testing utf-8 body" expected.body = "åœö blah" - expected.from = TestMailer.quote_address_if_necessary @recipient, "utf-8" - expected.cc = TestMailer.quote_address_if_necessary @recipient, "utf-8" - expected.bcc = TestMailer.quote_address_if_necessary @recipient, "utf-8" + expected.from = quote_address_if_necessary @recipient, "utf-8" + expected.cc = quote_address_if_necessary @recipient, "utf-8" + expected.bcc = quote_address_if_necessary @recipient, "utf-8" expected.date = Time.local 2004, 12, 12 created = TestMailer.create_utf8_body @recipient @@ -354,12 +379,12 @@ EOF def test_multiple_utf8_recipients @recipient = ["\"Foo áëô îü\" ", "\"Example Recipient\" "] expected = new_mail "utf-8" - expected.to = TestMailer.quote_address_if_necessary @recipient, "utf-8" + expected.to = quote_address_if_necessary @recipient, "utf-8" expected.subject = "testing utf-8 body" expected.body = "åœö blah" - expected.from = TestMailer.quote_address_if_necessary @recipient.first, "utf-8" - expected.cc = TestMailer.quote_address_if_necessary @recipient, "utf-8" - expected.bcc = TestMailer.quote_address_if_necessary @recipient, "utf-8" + expected.from = quote_address_if_necessary @recipient.first, "utf-8" + expected.cc = quote_address_if_necessary @recipient, "utf-8" + expected.bcc = quote_address_if_necessary @recipient, "utf-8" expected.date = Time.local 2004, 12, 12 created = TestMailer.create_utf8_body @recipient @@ -400,5 +425,26 @@ EOF assert_nothing_raised { mail.body } end + def test_explicitly_multipart_messages + mail = TestMailer.create_explicitly_multipart_example(@recipient) + assert_equal 3, mail.parts.length + assert_equal "text/plain", mail.parts[0].content_type + + assert_equal "text/html", mail.parts[1].content_type + assert_equal "inline", mail.parts[1].content_disposition + + assert_equal "image/jpeg", mail.parts[2].content_type + assert_equal "attachment", mail.parts[2].content_disposition + assert_equal "foo.jpg", mail.parts[2].sub_header("content-disposition", "filename") + assert_equal "foo.jpg", mail.parts[2].sub_header("content-type", "name") + end + + def test_implicitly_multipart_messages + mail = TestMailer.create_implicitly_multipart_example(@recipient) + assert_equal 2, mail.parts.length + assert_equal "text/html", mail.parts[0].content_type + assert_equal "text/plain", mail.parts[1].content_type + end + end -- cgit v1.2.3