mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Merge branch 'mail'
This commit is contained in:
@@ -1,3 +1,36 @@
|
||||
*Mail Integration
|
||||
|
||||
* ActionMailer::Base :default_implicit_parts_order now is in the sequence of the order you want, no
|
||||
reversing of ordering takes place. The default order now is text/plain, then text/enriched, then
|
||||
text/html and then any other part that is not one of these three.
|
||||
|
||||
* Mail does not have "quoted_body", "quoted_subject" etc. All of these are accessed via body.encoded,
|
||||
subject.encoded etc
|
||||
|
||||
* Every part of a Mail object returns an object, never a string. So Mail.body returns a Mail::Body
|
||||
class object, need to call #encoded or #decoded to get the string you want.
|
||||
|
||||
* By default, a field will return the #decoded value when you send it :to_s and any object that
|
||||
is a container (like header, body etc) will return #encoded value when you send it :to_s
|
||||
|
||||
* Mail::Message#set_content_type does not exist, it is simply Mail::Message#content_type
|
||||
|
||||
* Every mail message gets a unique message_id unless you specify one, had to change all the tests that
|
||||
check for equality with expected.encoded == actual.encoded to first replace their message_ids with
|
||||
control values
|
||||
|
||||
* Mail now has a proper concept of parts, remove the ActionMailer::Part and ActionMailer::PartContainer classes
|
||||
|
||||
* Calling #encoded on any object returns it as a string ready to go into the output stream of an email, this
|
||||
means it includes the \r\n at the end of the lines and the object is pre-wrapped with \r\n\t if it is a
|
||||
header field. Also, the "encoded" value includes the field name if it is a header field.
|
||||
|
||||
* Attachments are only the actual attachment, with filename etc. A part contains an attachment. The part
|
||||
has the content_type etc. So attachments.last.content_type is invalid. But parts.last.content_type
|
||||
|
||||
* There is no idea of a "sub_head" in Mail. A part is just a Message with some extra functionality, so it
|
||||
just has a "header" like a normal mail message
|
||||
|
||||
*2.3.2 [Final] (March 15, 2009)*
|
||||
|
||||
* Fixed that ActionMailer should send correctly formatted Return-Path in MAIL FROM for SMTP #1842 [Matt Jones]
|
||||
|
||||
@@ -11,6 +11,7 @@ Gem::Specification.new do |s|
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
|
||||
s.add_dependency('actionpack', '= 3.0.pre')
|
||||
s.add_dependency('mail', '~> 1.4.2')
|
||||
|
||||
s.files = Dir['CHANGELOG', 'README', 'MIT-LICENSE', 'lib/**/*']
|
||||
s.has_rdoc = true
|
||||
|
||||
@@ -31,13 +31,17 @@ module ActionMailer
|
||||
extend ::ActiveSupport::Autoload
|
||||
|
||||
autoload :AdvAttrAccessor
|
||||
autoload :DeprecatedBody
|
||||
autoload :Base
|
||||
autoload :DeliveryMethod
|
||||
autoload :DeprecatedBody
|
||||
autoload :MailHelper
|
||||
autoload :Part
|
||||
autoload :PartContainer
|
||||
autoload :Quoting
|
||||
autoload :TestCase
|
||||
autoload :TestHelper
|
||||
autoload :Utils
|
||||
end
|
||||
|
||||
module Text
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Format, 'action_mailer/vendor/text_format'
|
||||
end
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
require 'active_support/core_ext/class'
|
||||
require 'action_mailer/part'
|
||||
require 'action_mailer/vendor/text_format'
|
||||
require 'action_mailer/vendor/tmail'
|
||||
require 'mail'
|
||||
|
||||
module ActionMailer #:nodoc:
|
||||
# Action Mailer allows you to send email from your application using a mailer model and views.
|
||||
#
|
||||
#
|
||||
# = Mailer Models
|
||||
#
|
||||
# To use Action Mailer, you need to create a mailer model.
|
||||
@@ -253,7 +250,7 @@ module ActionMailer #:nodoc:
|
||||
# and appear last in the mime encoded message. You can also pick a different order from inside a method with
|
||||
# +implicit_parts_order+.
|
||||
class Base < AbstractController::Base
|
||||
include PartContainer, Quoting
|
||||
include Quoting
|
||||
extend AdvAttrAccessor
|
||||
|
||||
include AbstractController::Rendering
|
||||
@@ -286,7 +283,13 @@ module ActionMailer #:nodoc:
|
||||
@@default_mime_version = "1.0"
|
||||
cattr_accessor :default_mime_version
|
||||
|
||||
@@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ]
|
||||
# This specifies the order that the parts of a multipart email will be. Usually you put
|
||||
# text/plain at the top so someone without a MIME capable email reader can read the plain
|
||||
# text of your email first.
|
||||
#
|
||||
# Any content type that is not listed here will be inserted in the order you add them to
|
||||
# the email after the content types you list here.
|
||||
@@default_implicit_parts_order = [ "text/plain", "text/enriched", "text/html" ]
|
||||
cattr_accessor :default_implicit_parts_order
|
||||
|
||||
@@protected_instance_variables = %w(@parts @mail)
|
||||
@@ -395,8 +398,7 @@ module ActionMailer #:nodoc:
|
||||
# end
|
||||
def receive(raw_email)
|
||||
logger.info "Received mail:\n #{raw_email}" unless logger.nil?
|
||||
mail = TMail::Mail.parse(raw_email)
|
||||
mail.base64_decode
|
||||
mail = Mail.new(raw_email)
|
||||
new.receive(mail)
|
||||
end
|
||||
|
||||
@@ -431,18 +433,45 @@ module ActionMailer #:nodoc:
|
||||
superclass_delegating_reader :delivery_method
|
||||
self.delivery_method = :smtp
|
||||
|
||||
# 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
|
||||
if custom_headers = params.delete(:headers)
|
||||
ActiveSupport::Deprecation.warn('Passing custom headers with :headers => {} is deprecated. ' <<
|
||||
'Please just pass in custom headers directly.', caller[0,10])
|
||||
params.merge!(custom_headers)
|
||||
end
|
||||
part = Mail::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)
|
||||
super # Run deprecation hooks
|
||||
|
||||
params = { :content_type => params } if String === params
|
||||
params = { :content_disposition => "attachment",
|
||||
:content_transfer_encoding => "base64" }.merge(params)
|
||||
|
||||
part(params, &block)
|
||||
end
|
||||
|
||||
# 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, *args) #:nodoc:
|
||||
def initialize(method_name=nil, *args)
|
||||
super()
|
||||
process(method_name, *args) if method_name
|
||||
end
|
||||
|
||||
# Process the mailer via the given +method_name+. The body will be
|
||||
# rendered and a new TMail::Mail object created.
|
||||
def process(method_name, *args) #:nodoc:
|
||||
# rendered and a new Mail object created.
|
||||
def process(method_name, *args)
|
||||
initialize_defaults(method_name)
|
||||
super
|
||||
|
||||
@@ -457,7 +486,7 @@ module ActionMailer #:nodoc:
|
||||
create_mail
|
||||
end
|
||||
|
||||
# Delivers a TMail::Mail object. By default, it delivers the cached mail
|
||||
# Delivers a Mail object. By default, it delivers the cached mail
|
||||
# object (from the <tt>create!</tt> method). If no cached mail object exists, and
|
||||
# no alternate has been given as the parameter, this will fail.
|
||||
def deliver!(mail = @mail)
|
||||
@@ -484,7 +513,7 @@ module ActionMailer #:nodoc:
|
||||
# Set up the default values for the various instance variables of this
|
||||
# mailer. Subclasses may override this method to provide different
|
||||
# defaults.
|
||||
def initialize_defaults(method_name)
|
||||
def initialize_defaults(method_name) #:nodoc:
|
||||
@charset ||= @@default_charset.dup
|
||||
@content_type ||= @@default_content_type.dup
|
||||
@implicit_parts_order ||= @@default_implicit_parts_order.dup
|
||||
@@ -500,29 +529,18 @@ module ActionMailer #:nodoc:
|
||||
super # Run deprecation hooks
|
||||
end
|
||||
|
||||
def create_parts
|
||||
def create_parts #:nodoc:
|
||||
super # Run deprecation hooks
|
||||
|
||||
if String === response_body
|
||||
@parts.unshift Part.new(
|
||||
:content_type => "text/plain",
|
||||
:disposition => "inline",
|
||||
:charset => charset,
|
||||
:body => response_body
|
||||
)
|
||||
@parts.unshift create_inline_part(response_body)
|
||||
else
|
||||
self.class.template_root.find_all(@template, {}, mailer_name).each do |template|
|
||||
@parts << Part.new(
|
||||
:content_type => template.mime_type ? template.mime_type.to_s : "text/plain",
|
||||
:disposition => "inline",
|
||||
:charset => charset,
|
||||
:body => render_to_body(:_template => template)
|
||||
)
|
||||
self.class.template_root.find_all(@template, {}, @mailer_name).each do |template|
|
||||
@parts << create_inline_part(render_to_body(:_template => template), template.mime_type)
|
||||
end
|
||||
|
||||
if @parts.size > 1
|
||||
@content_type = "multipart/alternative" if @content_type !~ /^multipart/
|
||||
@parts = sort_parts(@parts, @implicit_parts_order)
|
||||
end
|
||||
|
||||
# If this is a multipart e-mail add the mime_version if it is not
|
||||
@@ -531,37 +549,19 @@ module ActionMailer #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
def sort_parts(parts, order = [])
|
||||
order = order.collect { |s| s.downcase }
|
||||
def create_inline_part(body, mime_type=nil) #:nodoc:
|
||||
ct = mime_type || "text/plain"
|
||||
main_type, sub_type = split_content_type(ct.to_s)
|
||||
|
||||
parts = parts.sort do |a, b|
|
||||
a_ct = a.content_type.downcase
|
||||
b_ct = b.content_type.downcase
|
||||
|
||||
a_in = order.include? a_ct
|
||||
b_in = order.include? b_ct
|
||||
|
||||
s = case
|
||||
when a_in && b_in
|
||||
order.index(a_ct) <=> order.index(b_ct)
|
||||
when a_in
|
||||
-1
|
||||
when b_in
|
||||
1
|
||||
else
|
||||
a_ct <=> b_ct
|
||||
end
|
||||
|
||||
# reverse the ordering because parts that come last are displayed
|
||||
# first in mail clients
|
||||
(s * -1)
|
||||
end
|
||||
|
||||
parts
|
||||
Mail::Part.new(
|
||||
:content_type => [main_type, sub_type, {:charset => charset}],
|
||||
:content_disposition => "inline",
|
||||
:body => body
|
||||
)
|
||||
end
|
||||
|
||||
def create_mail
|
||||
m = TMail::Mail.new
|
||||
def create_mail #:nodoc:
|
||||
m = Mail.new
|
||||
|
||||
m.subject, = quote_any_if_necessary(charset, subject)
|
||||
m.to, m.from = quote_any_address_if_necessary(charset, recipients, from)
|
||||
@@ -574,19 +574,43 @@ module ActionMailer #:nodoc:
|
||||
headers.each { |k, v| m[k] = v }
|
||||
|
||||
real_content_type, ctype_attrs = parse_content_type
|
||||
main_type, sub_type = split_content_type(real_content_type)
|
||||
|
||||
if @parts.empty?
|
||||
m.set_content_type(real_content_type, nil, ctype_attrs)
|
||||
m.body = normalize_new_lines(body)
|
||||
elsif @parts.size == 1 && @parts.first.parts.empty?
|
||||
m.set_content_type(real_content_type, nil, ctype_attrs)
|
||||
m.body = normalize_new_lines(@parts.first.body)
|
||||
if @parts.size == 1 && @parts.first.parts.empty?
|
||||
m.content_type([main_type, sub_type, ctype_attrs])
|
||||
m.body = @parts.first.body.encoded
|
||||
else
|
||||
setup_multiple_parts(m, real_content_type, ctype_attrs)
|
||||
@parts.each do |p|
|
||||
m.add_part(p)
|
||||
end
|
||||
|
||||
m.body.set_sort_order(@implicit_parts_order)
|
||||
m.body.sort_parts!
|
||||
|
||||
if real_content_type =~ /multipart/
|
||||
ctype_attrs.delete "charset"
|
||||
m.content_type([main_type, sub_type, ctype_attrs])
|
||||
end
|
||||
end
|
||||
|
||||
m.content_transfer_encoding = '8bit' unless m.body.only_us_ascii?
|
||||
|
||||
@mail = m
|
||||
end
|
||||
|
||||
def split_content_type(ct) #:nodoc:
|
||||
ct.to_s.split("/")
|
||||
end
|
||||
|
||||
def parse_content_type(defaults=nil) #:nodoc:
|
||||
if @content_type.blank?
|
||||
[ nil, {} ]
|
||||
else
|
||||
ctype, *attrs = @content_type.split(/;\s*/)
|
||||
attrs = attrs.inject({}) { |h,s| k,v = s.split(/\=/, 2); h[k] = v; h }
|
||||
[ctype, {"charset" => @charset}.merge(attrs)]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ module ActionMailer
|
||||
def perform_delivery(mail)
|
||||
FileUtils.mkdir_p settings[:location]
|
||||
|
||||
(mail.to + mail.cc + mail.bcc).uniq.each do |to|
|
||||
mail.destinations.uniq.each do |to|
|
||||
::File.open(::File.join(settings[:location], to), 'a') { |f| f.write(mail) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,8 +16,7 @@ module ActionMailer
|
||||
|
||||
def perform_delivery(mail)
|
||||
destinations = mail.destinations
|
||||
mail.ready_to_send
|
||||
sender = (mail['return-path'] && mail['return-path'].spec) || Array(mail.from).first
|
||||
sender = (mail['return-path'] && mail['return-path'].address) || mail['from']
|
||||
|
||||
smtp = Net::SMTP.new(settings[:address], settings[:port])
|
||||
smtp.enable_starttls_auto if settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto)
|
||||
|
||||
@@ -15,6 +15,14 @@ module ActionMailer
|
||||
@body ||= {}
|
||||
end
|
||||
|
||||
def attachment(params, &block)
|
||||
if params[:body]
|
||||
ActiveSupport::Deprecation.warn('attachment :body => "string" is deprecated. To set the body of an attachment ' <<
|
||||
'please use :data instead, like attachment :data => "string".', caller[0,10])
|
||||
params[:data] = params.delete(:body)
|
||||
end
|
||||
end
|
||||
|
||||
def create_parts
|
||||
if String === @body
|
||||
ActiveSupport::Deprecation.warn('body is deprecated. To set the body with a text ' <<
|
||||
|
||||
@@ -12,7 +12,7 @@ module ActionMailer
|
||||
# Make list points stand on their own line
|
||||
formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
||||
formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
||||
|
||||
|
||||
formatted
|
||||
end
|
||||
|
||||
@@ -21,4 +21,4 @@ module ActionMailer
|
||||
@controller
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
module ActionMailer
|
||||
# Represents a subpart of an email message. It shares many similar
|
||||
# attributes of ActionMailer::Base. Although you can create parts manually
|
||||
# and add them to the +parts+ list of the mailer, it is easier
|
||||
# to use the helper methods in ActionMailer::PartContainer.
|
||||
class Part
|
||||
include PartContainer
|
||||
extend AdvAttrAccessor
|
||||
|
||||
# Represents the body of the part, as a string. This should not be a
|
||||
# Hash (like ActionMailer::Base), but if you want a template to be rendered
|
||||
# into the body of a subpart you can do it with the mailer's +render+ method
|
||||
# and assign the result here.
|
||||
adv_attr_accessor :body
|
||||
|
||||
# Specify the charset for this subpart. By default, it will be the charset
|
||||
# of the containing part or mailer.
|
||||
adv_attr_accessor :charset
|
||||
|
||||
# The content disposition of this part, typically either "inline" or
|
||||
# "attachment".
|
||||
adv_attr_accessor :content_disposition
|
||||
|
||||
# The content type of the part.
|
||||
adv_attr_accessor :content_type
|
||||
|
||||
# The filename to use for this subpart (usually for attachments).
|
||||
adv_attr_accessor :filename
|
||||
|
||||
# Accessor for specifying additional headers to include with this part.
|
||||
adv_attr_accessor :headers
|
||||
|
||||
# The transfer encoding to use for this subpart, like "base64" or
|
||||
# "quoted-printable".
|
||||
adv_attr_accessor :transfer_encoding
|
||||
|
||||
# Create a new part from the given +params+ hash. The valid params keys
|
||||
# correspond to the accessors.
|
||||
def initialize(params)
|
||||
@content_type = params[:content_type]
|
||||
@content_disposition = params[:disposition] || "inline"
|
||||
@charset = params[:charset]
|
||||
@body = params[:body]
|
||||
@filename = params[:filename]
|
||||
@transfer_encoding = params[:transfer_encoding] || "quoted-printable"
|
||||
@headers = params[:headers] || {}
|
||||
@parts = []
|
||||
end
|
||||
|
||||
# Convert the part to a mail object which can be included in the parts
|
||||
# list of another mail object.
|
||||
def to_mail(defaults)
|
||||
part = TMail::Mail.new
|
||||
|
||||
real_content_type, ctype_attrs = parse_content_type(defaults)
|
||||
|
||||
if @parts.empty?
|
||||
part.content_transfer_encoding = transfer_encoding || "quoted-printable"
|
||||
case (transfer_encoding || "").downcase
|
||||
when "base64" then
|
||||
part.body = TMail::Base64.folding_encode(body)
|
||||
when "quoted-printable"
|
||||
part.body = [normalize_new_lines(body)].pack("M*")
|
||||
else
|
||||
part.body = body
|
||||
end
|
||||
|
||||
# Always set the content_type after setting the body and or parts!
|
||||
# Also don't set filename and name when there is none (like in
|
||||
# non-attachment parts)
|
||||
if content_disposition == "attachment"
|
||||
ctype_attrs.delete "charset"
|
||||
part.set_content_type(real_content_type, nil,
|
||||
squish("name" => filename).merge(ctype_attrs))
|
||||
part.set_content_disposition(content_disposition,
|
||||
squish("filename" => filename).merge(ctype_attrs))
|
||||
else
|
||||
part.set_content_type(real_content_type, nil, ctype_attrs)
|
||||
part.set_content_disposition(content_disposition)
|
||||
end
|
||||
else
|
||||
if String === body
|
||||
@parts.unshift Part.new(:charset => charset, :body => @body, :content_type => 'text/plain')
|
||||
@body = nil
|
||||
end
|
||||
|
||||
setup_multiple_parts(part, real_content_type, ctype_attrs)
|
||||
end
|
||||
|
||||
headers.each { |k,v| part[k] = v }
|
||||
|
||||
part
|
||||
end
|
||||
|
||||
private
|
||||
def squish(values={})
|
||||
values.delete_if { |k,v| v.nil? }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,71 +0,0 @@
|
||||
module ActionMailer
|
||||
# Accessors and helpers that ActionMailer::Base and ActionMailer::Part have
|
||||
# in common. Using these helpers you can easily add subparts or attachments
|
||||
# to your message:
|
||||
#
|
||||
# def my_mail_message(...)
|
||||
# ...
|
||||
# part "text/plain" do |p|
|
||||
# p.body "hello, world"
|
||||
# p.transfer_encoding "base64"
|
||||
# end
|
||||
#
|
||||
# attachment "image/jpg" do |a|
|
||||
# a.body = File.read("hello.jpg")
|
||||
# a.filename = "hello.jpg"
|
||||
# end
|
||||
# end
|
||||
module PartContainer
|
||||
# The list of subparts of this container
|
||||
attr_reader :parts
|
||||
|
||||
# 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
|
||||
|
||||
private
|
||||
|
||||
def normalize_new_lines(text) #:nodoc:
|
||||
text.to_s.gsub(/\r\n?/, "\n")
|
||||
end
|
||||
|
||||
def setup_multiple_parts(mailer, real_content_type, ctype_attrs) #:nodoc:
|
||||
@parts.each do |p|
|
||||
part = (TMail::Mail === p ? p : p.to_mail(self))
|
||||
mailer.parts << part
|
||||
end
|
||||
|
||||
if real_content_type =~ /multipart/
|
||||
ctype_attrs.delete "charset"
|
||||
mailer.set_content_type(real_content_type, nil, ctype_attrs)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_content_type(defaults=nil) #:nodoc:
|
||||
if content_type.blank?
|
||||
return defaults ?
|
||||
[ defaults.content_type, { 'charset' => defaults.charset } ] :
|
||||
[ nil, {} ]
|
||||
end
|
||||
ctype, *attrs = content_type.split(/;\s*/)
|
||||
attrs = attrs.inject({}) { |h,s| k,v = s.split(/=/, 2); h[k] = v; h }
|
||||
[ctype, {"charset" => charset || defaults && defaults.charset}.merge(attrs)]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -43,7 +43,7 @@ module ActionMailer
|
||||
# "to", "from", "cc", "bcc" and "reply-to" headers.
|
||||
def quote_address_if_necessary(address, charset)
|
||||
if Array === address
|
||||
address.map { |a| quote_address_if_necessary(a, charset) }
|
||||
address.map { |a| quote_address_if_necessary(a, charset) }.join(", ")
|
||||
elsif address =~ /^(\S.*)\s+(<.*>)$/
|
||||
address = $2
|
||||
phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require 'active_support/test_case'
|
||||
require 'mail'
|
||||
|
||||
module ActionMailer
|
||||
class NonInferrableMailerError < ::StandardError
|
||||
@@ -43,8 +44,8 @@ module ActionMailer
|
||||
end
|
||||
|
||||
def set_expected_mail
|
||||
@expected = TMail::Mail.new
|
||||
@expected.set_content_type "text", "plain", { "charset" => charset }
|
||||
@expected = Mail.new
|
||||
@expected.content_type ["text", "plain", { "charset" => charset }]
|
||||
@expected.mime_version = '1.0'
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
require 'tmail/version'
|
||||
require 'tmail/mail'
|
||||
require 'tmail/mailbox'
|
||||
require 'tmail/core_extensions'
|
||||
require 'tmail/net'
|
||||
@@ -1,426 +0,0 @@
|
||||
=begin rdoc
|
||||
|
||||
= Address handling class
|
||||
|
||||
=end
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/encode'
|
||||
require 'tmail/parser'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
# = Class Address
|
||||
#
|
||||
# Provides a complete handling library for email addresses. Can parse a string of an
|
||||
# address directly or take in preformatted addresses themselves. Allows you to add
|
||||
# and remove phrases from the front of the address and provides a compare function for
|
||||
# email addresses.
|
||||
#
|
||||
# == Parsing and Handling a Valid Address:
|
||||
#
|
||||
# Just pass the email address in as a string to Address.parse:
|
||||
#
|
||||
# email = TMail::Address.parse('Mikel Lindsaar <mikel@lindsaar.net>)
|
||||
# #=> #<TMail::Address mikel@lindsaar.net>
|
||||
# email.address
|
||||
# #=> "mikel@lindsaar.net"
|
||||
# email.local
|
||||
# #=> "mikel"
|
||||
# email.domain
|
||||
# #=> "lindsaar.net"
|
||||
# email.name # Aliased as phrase as well
|
||||
# #=> "Mikel Lindsaar"
|
||||
#
|
||||
# == Detecting an Invalid Address
|
||||
#
|
||||
# If you want to check the syntactical validity of an email address, just pass it to
|
||||
# Address.parse and catch any SyntaxError:
|
||||
#
|
||||
# begin
|
||||
# TMail::Mail.parse("mikel 2@@@@@ me .com")
|
||||
# rescue TMail::SyntaxError
|
||||
# puts("Invalid Email Address Detected")
|
||||
# else
|
||||
# puts("Address is valid")
|
||||
# end
|
||||
# #=> "Invalid Email Address Detected"
|
||||
class Address
|
||||
|
||||
include TextUtils #:nodoc:
|
||||
|
||||
# Sometimes you need to parse an address, TMail can do it for you and provide you with
|
||||
# a fairly robust method of detecting a valid address.
|
||||
#
|
||||
# Takes in a string, returns a TMail::Address object.
|
||||
#
|
||||
# Raises a TMail::SyntaxError on invalid email format
|
||||
def Address.parse( str )
|
||||
Parser.parse :ADDRESS, special_quote_address(str)
|
||||
end
|
||||
|
||||
def Address.special_quote_address(str) #:nodoc:
|
||||
# Takes a string which is an address and adds quotation marks to special
|
||||
# edge case methods that the RACC parser can not handle.
|
||||
#
|
||||
# Right now just handles two edge cases:
|
||||
#
|
||||
# Full stop as the last character of the display name:
|
||||
# Mikel L. <mikel@me.com>
|
||||
# Returns:
|
||||
# "Mikel L." <mikel@me.com>
|
||||
#
|
||||
# Unquoted @ symbol in the display name:
|
||||
# mikel@me.com <mikel@me.com>
|
||||
# Returns:
|
||||
# "mikel@me.com" <mikel@me.com>
|
||||
#
|
||||
# Any other address not matching these patterns just gets returned as is.
|
||||
case
|
||||
# This handles the missing "" in an older version of Apple Mail.app
|
||||
# around the display name when the display name contains a '@'
|
||||
# like 'mikel@me.com <mikel@me.com>'
|
||||
# Just quotes it to: '"mikel@me.com" <mikel@me.com>'
|
||||
when str =~ /\A([^"].+@.+[^"])\s(<.*?>)\Z/
|
||||
return "\"#{$1}\" #{$2}"
|
||||
# This handles cases where 'Mikel A. <mikel@me.com>' which is a trailing
|
||||
# full stop before the address section. Just quotes it to
|
||||
# '"Mikel A. <mikel@me.com>"
|
||||
when str =~ /\A(.*?\.)\s(<.*?>)\Z/
|
||||
return "\"#{$1}\" #{$2}"
|
||||
else
|
||||
str
|
||||
end
|
||||
end
|
||||
|
||||
def address_group? #:nodoc:
|
||||
false
|
||||
end
|
||||
|
||||
# Address.new(local, domain)
|
||||
#
|
||||
# Accepts:
|
||||
#
|
||||
# * local - Left of the at symbol
|
||||
#
|
||||
# * domain - Array of the domain split at the periods.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# Address.new("mikel", ["lindsaar", "net"])
|
||||
# #=> "#<TMail::Address mikel@lindsaar.net>"
|
||||
def initialize( local, domain )
|
||||
if domain
|
||||
domain.each do |s|
|
||||
raise SyntaxError, 'empty word in domain' if s.empty?
|
||||
end
|
||||
end
|
||||
|
||||
# This is to catch an unquoted "@" symbol in the local part of the
|
||||
# address. Handles addresses like <"@"@me.com> and makes sure they
|
||||
# stay like <"@"@me.com> (previously were becoming <@@me.com>)
|
||||
if local && (local.join == '@' || local.join =~ /\A[^"].*?@.*?[^"]\Z/)
|
||||
@local = "\"#{local.join}\""
|
||||
else
|
||||
@local = local
|
||||
end
|
||||
|
||||
@domain = domain
|
||||
@name = nil
|
||||
@routes = []
|
||||
end
|
||||
|
||||
# Provides the name or 'phrase' of the email address.
|
||||
#
|
||||
# For Example:
|
||||
#
|
||||
# email = TMail::Address.parse("Mikel Lindsaar <mikel@lindsaar.net>")
|
||||
# email.name
|
||||
# #=> "Mikel Lindsaar"
|
||||
def name
|
||||
@name
|
||||
end
|
||||
|
||||
# Setter method for the name or phrase of the email
|
||||
#
|
||||
# For Example:
|
||||
#
|
||||
# email = TMail::Address.parse("mikel@lindsaar.net")
|
||||
# email.name
|
||||
# #=> nil
|
||||
# email.name = "Mikel Lindsaar"
|
||||
# email.to_s
|
||||
# #=> "Mikel Lindsaar <mikel@me.com>"
|
||||
def name=( str )
|
||||
@name = str
|
||||
@name = nil if str and str.empty?
|
||||
end
|
||||
|
||||
#:stopdoc:
|
||||
alias phrase name
|
||||
alias phrase= name=
|
||||
#:startdoc:
|
||||
|
||||
# This is still here from RFC 822, and is now obsolete per RFC2822 Section 4.
|
||||
#
|
||||
# "When interpreting addresses, the route portion SHOULD be ignored."
|
||||
#
|
||||
# It is still here, so you can access it.
|
||||
#
|
||||
# Routes return the route portion at the front of the email address, if any.
|
||||
#
|
||||
# For Example:
|
||||
# email = TMail::Address.parse( "<@sa,@another:Mikel@me.com>")
|
||||
# => #<TMail::Address Mikel@me.com>
|
||||
# email.to_s
|
||||
# => "<@sa,@another:Mikel@me.com>"
|
||||
# email.routes
|
||||
# => ["sa", "another"]
|
||||
def routes
|
||||
@routes
|
||||
end
|
||||
|
||||
def inspect #:nodoc:
|
||||
"#<#{self.class} #{address()}>"
|
||||
end
|
||||
|
||||
# Returns the local part of the email address
|
||||
#
|
||||
# For Example:
|
||||
#
|
||||
# email = TMail::Address.parse("mikel@lindsaar.net")
|
||||
# email.local
|
||||
# #=> "mikel"
|
||||
def local
|
||||
return nil unless @local
|
||||
return '""' if @local.size == 1 and @local[0].empty?
|
||||
# Check to see if it is an array before trying to map it
|
||||
if @local.respond_to?(:map)
|
||||
@local.map {|i| quote_atom(i) }.join('.')
|
||||
else
|
||||
quote_atom(@local)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the domain part of the email address
|
||||
#
|
||||
# For Example:
|
||||
#
|
||||
# email = TMail::Address.parse("mikel@lindsaar.net")
|
||||
# email.local
|
||||
# #=> "lindsaar.net"
|
||||
def domain
|
||||
return nil unless @domain
|
||||
join_domain(@domain)
|
||||
end
|
||||
|
||||
# Returns the full specific address itself
|
||||
#
|
||||
# For Example:
|
||||
#
|
||||
# email = TMail::Address.parse("mikel@lindsaar.net")
|
||||
# email.address
|
||||
# #=> "mikel@lindsaar.net"
|
||||
def spec
|
||||
s = self.local
|
||||
d = self.domain
|
||||
if s and d
|
||||
s + '@' + d
|
||||
else
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
alias address spec
|
||||
|
||||
# Provides == function to the email. Only checks the actual address
|
||||
# and ignores the name/phrase component
|
||||
#
|
||||
# For Example
|
||||
#
|
||||
# addr1 = TMail::Address.parse("My Address <mikel@lindsaar.net>")
|
||||
# #=> "#<TMail::Address mikel@lindsaar.net>"
|
||||
# addr2 = TMail::Address.parse("Another <mikel@lindsaar.net>")
|
||||
# #=> "#<TMail::Address mikel@lindsaar.net>"
|
||||
# addr1 == addr2
|
||||
# #=> true
|
||||
def ==( other )
|
||||
other.respond_to? :spec and self.spec == other.spec
|
||||
end
|
||||
|
||||
alias eql? ==
|
||||
|
||||
# Provides a unique hash value for this record against the local and domain
|
||||
# parts, ignores the name/phrase value
|
||||
#
|
||||
# email = TMail::Address.parse("mikel@lindsaar.net")
|
||||
# email.hash
|
||||
# #=> 18767598
|
||||
def hash
|
||||
@local.hash ^ @domain.hash
|
||||
end
|
||||
|
||||
# Duplicates a TMail::Address object returning the duplicate
|
||||
#
|
||||
# addr1 = TMail::Address.parse("mikel@lindsaar.net")
|
||||
# addr2 = addr1.dup
|
||||
# addr1.id == addr2.id
|
||||
# #=> false
|
||||
def dup
|
||||
obj = self.class.new(@local.dup, @domain.dup)
|
||||
obj.name = @name.dup if @name
|
||||
obj.routes.replace @routes
|
||||
obj
|
||||
end
|
||||
|
||||
include StrategyInterface #:nodoc:
|
||||
|
||||
def accept( strategy, dummy1 = nil, dummy2 = nil ) #:nodoc:
|
||||
unless @local
|
||||
strategy.meta '<>' # empty return-path
|
||||
return
|
||||
end
|
||||
|
||||
spec_p = (not @name and @routes.empty?)
|
||||
if @name
|
||||
strategy.phrase @name
|
||||
strategy.space
|
||||
end
|
||||
tmp = spec_p ? '' : '<'
|
||||
unless @routes.empty?
|
||||
tmp << @routes.map {|i| '@' + i }.join(',') << ':'
|
||||
end
|
||||
tmp << self.spec
|
||||
tmp << '>' unless spec_p
|
||||
strategy.meta tmp
|
||||
strategy.lwsp ''
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class AddressGroup
|
||||
|
||||
include Enumerable
|
||||
|
||||
def address_group?
|
||||
true
|
||||
end
|
||||
|
||||
def initialize( name, addrs )
|
||||
@name = name
|
||||
@addresses = addrs
|
||||
end
|
||||
|
||||
attr_reader :name
|
||||
|
||||
def ==( other )
|
||||
other.respond_to? :to_a and @addresses == other.to_a
|
||||
end
|
||||
|
||||
alias eql? ==
|
||||
|
||||
def hash
|
||||
map {|i| i.hash }.hash
|
||||
end
|
||||
|
||||
def []( idx )
|
||||
@addresses[idx]
|
||||
end
|
||||
|
||||
def size
|
||||
@addresses.size
|
||||
end
|
||||
|
||||
def empty?
|
||||
@addresses.empty?
|
||||
end
|
||||
|
||||
def each( &block )
|
||||
@addresses.each(&block)
|
||||
end
|
||||
|
||||
def to_a
|
||||
@addresses.dup
|
||||
end
|
||||
|
||||
alias to_ary to_a
|
||||
|
||||
def include?( a )
|
||||
@addresses.include? a
|
||||
end
|
||||
|
||||
def flatten
|
||||
set = []
|
||||
@addresses.each do |a|
|
||||
if a.respond_to? :flatten
|
||||
set.concat a.flatten
|
||||
else
|
||||
set.push a
|
||||
end
|
||||
end
|
||||
set
|
||||
end
|
||||
|
||||
def each_address( &block )
|
||||
flatten.each(&block)
|
||||
end
|
||||
|
||||
def add( a )
|
||||
@addresses.push a
|
||||
end
|
||||
|
||||
alias push add
|
||||
|
||||
def delete( a )
|
||||
@addresses.delete a
|
||||
end
|
||||
|
||||
include StrategyInterface
|
||||
|
||||
def accept( strategy, dummy1 = nil, dummy2 = nil )
|
||||
strategy.phrase @name
|
||||
strategy.meta ':'
|
||||
strategy.space
|
||||
first = true
|
||||
each do |mbox|
|
||||
if first
|
||||
first = false
|
||||
else
|
||||
strategy.meta ','
|
||||
end
|
||||
strategy.space
|
||||
mbox.accept strategy
|
||||
end
|
||||
strategy.meta ';'
|
||||
strategy.lwsp ''
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
@@ -1,46 +0,0 @@
|
||||
=begin rdoc
|
||||
|
||||
= Attachment handling file
|
||||
|
||||
=end
|
||||
|
||||
require 'stringio'
|
||||
|
||||
module TMail
|
||||
class Attachment < StringIO
|
||||
attr_accessor :original_filename, :content_type
|
||||
end
|
||||
|
||||
class Mail
|
||||
def has_attachments?
|
||||
multipart? && parts.any? { |part| attachment?(part) }
|
||||
end
|
||||
|
||||
def attachment?(part)
|
||||
part.disposition_is_attachment? || part.content_type_is_text?
|
||||
end
|
||||
|
||||
def attachments
|
||||
if multipart?
|
||||
parts.collect { |part|
|
||||
if part.multipart?
|
||||
part.attachments
|
||||
elsif attachment?(part)
|
||||
content = part.body # unquoted automatically by TMail#body
|
||||
file_name = (part['content-location'] &&
|
||||
part['content-location'].body) ||
|
||||
part.sub_header("content-type", "name") ||
|
||||
part.sub_header("content-disposition", "filename")
|
||||
|
||||
next if file_name.blank? || content.blank?
|
||||
|
||||
attachment = Attachment.new(content)
|
||||
attachment.original_filename = file_name.strip
|
||||
attachment.content_type = part.content_type
|
||||
attachment
|
||||
end
|
||||
}.flatten.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,46 +0,0 @@
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
#:stopdoc:
|
||||
module TMail
|
||||
module Base64
|
||||
|
||||
module_function
|
||||
|
||||
def folding_encode( str, eol = "\n", limit = 60 )
|
||||
[str].pack('m')
|
||||
end
|
||||
|
||||
def encode( str )
|
||||
[str].pack('m').tr( "\r\n", '' )
|
||||
end
|
||||
|
||||
def decode( str, strict = false )
|
||||
str.unpack('m').first
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
#:startdoc:
|
||||
@@ -1,41 +0,0 @@
|
||||
#:stopdoc:
|
||||
unless Enumerable.method_defined?(:map)
|
||||
module Enumerable #:nodoc:
|
||||
alias map collect
|
||||
end
|
||||
end
|
||||
|
||||
unless Enumerable.method_defined?(:select)
|
||||
module Enumerable #:nodoc:
|
||||
alias select find_all
|
||||
end
|
||||
end
|
||||
|
||||
unless Enumerable.method_defined?(:reject)
|
||||
module Enumerable #:nodoc:
|
||||
def reject
|
||||
result = []
|
||||
each do |i|
|
||||
result.push i unless yield(i)
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Enumerable.method_defined?(:sort_by)
|
||||
module Enumerable #:nodoc:
|
||||
def sort_by
|
||||
map {|i| [yield(i), i] }.sort.map {|val, i| i }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless File.respond_to?(:read)
|
||||
def File.read(fname) #:nodoc:
|
||||
File.open(fname) {|f|
|
||||
return f.read
|
||||
}
|
||||
end
|
||||
end
|
||||
#:startdoc:
|
||||
@@ -1,67 +0,0 @@
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
#:stopdoc:
|
||||
module TMail
|
||||
|
||||
class Config
|
||||
|
||||
def initialize( strict )
|
||||
@strict_parse = strict
|
||||
@strict_base64decode = strict
|
||||
end
|
||||
|
||||
def strict_parse?
|
||||
@strict_parse
|
||||
end
|
||||
|
||||
attr_writer :strict_parse
|
||||
|
||||
def strict_base64decode?
|
||||
@strict_base64decode
|
||||
end
|
||||
|
||||
attr_writer :strict_base64decode
|
||||
|
||||
def new_body_port( mail )
|
||||
StringPort.new
|
||||
end
|
||||
|
||||
alias new_preamble_port new_body_port
|
||||
alias new_part_port new_body_port
|
||||
|
||||
end
|
||||
|
||||
DEFAULT_CONFIG = Config.new(false)
|
||||
DEFAULT_STRICT_CONFIG = Config.new(true)
|
||||
|
||||
def Config.to_config( arg )
|
||||
return DEFAULT_STRICT_CONFIG if arg == true
|
||||
return DEFAULT_CONFIG if arg == false
|
||||
arg or DEFAULT_CONFIG
|
||||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
@@ -1,63 +0,0 @@
|
||||
#:stopdoc:
|
||||
unless Object.respond_to?(:blank?)
|
||||
class Object
|
||||
# Check first to see if we are in a Rails environment, no need to
|
||||
# define these methods if we are
|
||||
|
||||
# An object is blank if it's nil, empty, or a whitespace string.
|
||||
# For example, "", " ", nil, [], and {} are blank.
|
||||
#
|
||||
# This simplifies
|
||||
# if !address.nil? && !address.empty?
|
||||
# to
|
||||
# if !address.blank?
|
||||
def blank?
|
||||
if respond_to?(:empty?) && respond_to?(:strip)
|
||||
empty? or strip.empty?
|
||||
elsif respond_to?(:empty?)
|
||||
empty?
|
||||
else
|
||||
!self
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NilClass
|
||||
def blank?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class FalseClass
|
||||
def blank?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class TrueClass
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class Array
|
||||
alias_method :blank?, :empty?
|
||||
end
|
||||
|
||||
class Hash
|
||||
alias_method :blank?, :empty?
|
||||
end
|
||||
|
||||
class String
|
||||
def blank?
|
||||
empty? || strip.empty?
|
||||
end
|
||||
end
|
||||
|
||||
class Numeric
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
#:startdoc:
|
||||
@@ -1,581 +0,0 @@
|
||||
#--
|
||||
# = COPYRIGHT:
|
||||
#
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
#:stopdoc:
|
||||
require 'nkf'
|
||||
require 'tmail/base64'
|
||||
require 'tmail/stringio'
|
||||
require 'tmail/utils'
|
||||
#:startdoc:
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
#:stopdoc:
|
||||
class << self
|
||||
attr_accessor :KCODE
|
||||
end
|
||||
self.KCODE = 'NONE'
|
||||
|
||||
module StrategyInterface
|
||||
|
||||
def create_dest( obj )
|
||||
case obj
|
||||
when nil
|
||||
StringOutput.new
|
||||
when String
|
||||
StringOutput.new(obj)
|
||||
when IO, StringOutput
|
||||
obj
|
||||
else
|
||||
raise TypeError, 'cannot handle this type of object for dest'
|
||||
end
|
||||
end
|
||||
module_function :create_dest
|
||||
|
||||
#:startdoc:
|
||||
# Returns the TMail object encoded and ready to be sent via SMTP etc.
|
||||
# You should call this before you are packaging up your email to
|
||||
# correctly escape all the values that need escaping in the email, line
|
||||
# wrap the email etc.
|
||||
#
|
||||
# It is also a good idea to call this before you marshal or serialize
|
||||
# a TMail object.
|
||||
#
|
||||
# For Example:
|
||||
#
|
||||
# email = TMail::Load(my_email_file)
|
||||
# email_to_send = email.encoded
|
||||
def encoded( eol = "\r\n", charset = 'j', dest = nil )
|
||||
accept_strategy Encoder, eol, charset, dest
|
||||
end
|
||||
|
||||
# Returns the TMail object decoded and ready to be used by you, your
|
||||
# program etc.
|
||||
#
|
||||
# You should call this before you are packaging up your email to
|
||||
# correctly escape all the values that need escaping in the email, line
|
||||
# wrap the email etc.
|
||||
#
|
||||
# For Example:
|
||||
#
|
||||
# email = TMail::Load(my_email_file)
|
||||
# email_to_send = email.encoded
|
||||
def decoded( eol = "\n", charset = 'e', dest = nil )
|
||||
# Turn the E-Mail into a string and return it with all
|
||||
# encoded characters decoded. alias for to_s
|
||||
accept_strategy Decoder, eol, charset, dest
|
||||
end
|
||||
|
||||
alias to_s decoded
|
||||
|
||||
def accept_strategy( klass, eol, charset, dest = nil ) #:nodoc:
|
||||
dest ||= ''
|
||||
accept klass.new( create_dest(dest), charset, eol )
|
||||
dest
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#:stopdoc:
|
||||
|
||||
###
|
||||
### MIME B encoding decoder
|
||||
###
|
||||
|
||||
class Decoder
|
||||
|
||||
include TextUtils
|
||||
|
||||
encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
|
||||
ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i
|
||||
|
||||
OUTPUT_ENCODING = {
|
||||
'EUC' => 'e',
|
||||
'SJIS' => 's',
|
||||
}
|
||||
|
||||
def self.decode( str, encoding = nil )
|
||||
encoding ||= (OUTPUT_ENCODING[TMail.KCODE] || 'j')
|
||||
opt = '-mS' + encoding
|
||||
str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
|
||||
end
|
||||
|
||||
def initialize( dest, encoding = nil, eol = "\n" )
|
||||
@f = StrategyInterface.create_dest(dest)
|
||||
@encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil
|
||||
@eol = eol
|
||||
end
|
||||
|
||||
def decode( str )
|
||||
self.class.decode(str, @encoding)
|
||||
end
|
||||
private :decode
|
||||
|
||||
def terminate
|
||||
end
|
||||
|
||||
def header_line( str )
|
||||
@f << decode(str)
|
||||
end
|
||||
|
||||
def header_name( nm )
|
||||
@f << nm << ': '
|
||||
end
|
||||
|
||||
def header_body( str )
|
||||
@f << decode(str)
|
||||
end
|
||||
|
||||
def space
|
||||
@f << ' '
|
||||
end
|
||||
|
||||
alias spc space
|
||||
|
||||
def lwsp( str )
|
||||
@f << str
|
||||
end
|
||||
|
||||
def meta( str )
|
||||
@f << str
|
||||
end
|
||||
|
||||
def text( str )
|
||||
@f << decode(str)
|
||||
end
|
||||
|
||||
def phrase( str )
|
||||
@f << quote_phrase(decode(str))
|
||||
end
|
||||
|
||||
def kv_pair( k, v )
|
||||
v = dquote(v) unless token_safe?(v)
|
||||
@f << k << '=' << v
|
||||
end
|
||||
|
||||
def puts( str = nil )
|
||||
@f << str if str
|
||||
@f << @eol
|
||||
end
|
||||
|
||||
def write( str )
|
||||
@f << str
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
###
|
||||
### MIME B-encoding encoder
|
||||
###
|
||||
|
||||
#
|
||||
# FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
|
||||
#
|
||||
class Encoder
|
||||
|
||||
include TextUtils
|
||||
|
||||
BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG)
|
||||
|
||||
def Encoder.encode( str )
|
||||
e = new()
|
||||
e.header_body str
|
||||
e.terminate
|
||||
e.dest.string
|
||||
end
|
||||
|
||||
SPACER = "\t"
|
||||
MAX_LINE_LEN = 78
|
||||
RFC_2822_MAX_LENGTH = 998
|
||||
|
||||
OPTIONS = {
|
||||
'EUC' => '-Ej -m0',
|
||||
'SJIS' => '-Sj -m0',
|
||||
'UTF8' => nil, # FIXME
|
||||
'NONE' => nil
|
||||
}
|
||||
|
||||
def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil )
|
||||
@f = StrategyInterface.create_dest(dest)
|
||||
@opt = OPTIONS[TMail.KCODE]
|
||||
@eol = eol
|
||||
@folded = false
|
||||
@preserve_quotes = true
|
||||
reset
|
||||
end
|
||||
|
||||
def preserve_quotes=( bool )
|
||||
@preserve_quotes
|
||||
end
|
||||
|
||||
def preserve_quotes
|
||||
@preserve_quotes
|
||||
end
|
||||
|
||||
def normalize_encoding( str )
|
||||
if @opt
|
||||
then NKF.nkf(@opt, str)
|
||||
else str
|
||||
end
|
||||
end
|
||||
|
||||
def reset
|
||||
@text = ''
|
||||
@lwsp = ''
|
||||
@curlen = 0
|
||||
end
|
||||
|
||||
def terminate
|
||||
add_lwsp ''
|
||||
reset
|
||||
end
|
||||
|
||||
def dest
|
||||
@f
|
||||
end
|
||||
|
||||
def puts( str = nil )
|
||||
@f << str if str
|
||||
@f << @eol
|
||||
end
|
||||
|
||||
def write( str )
|
||||
@f << str
|
||||
end
|
||||
|
||||
#
|
||||
# add
|
||||
#
|
||||
|
||||
def header_line( line )
|
||||
scanadd line
|
||||
end
|
||||
|
||||
def header_name( name )
|
||||
add_text name.split(/-/).map {|i| i.capitalize }.join('-')
|
||||
add_text ':'
|
||||
add_lwsp ' '
|
||||
end
|
||||
|
||||
def header_body( str )
|
||||
scanadd normalize_encoding(str)
|
||||
end
|
||||
|
||||
def space
|
||||
add_lwsp ' '
|
||||
end
|
||||
|
||||
alias spc space
|
||||
|
||||
def lwsp( str )
|
||||
add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '')
|
||||
end
|
||||
|
||||
def meta( str )
|
||||
add_text str
|
||||
end
|
||||
|
||||
def text( str )
|
||||
scanadd normalize_encoding(str)
|
||||
end
|
||||
|
||||
def phrase( str )
|
||||
str = normalize_encoding(str)
|
||||
if CONTROL_CHAR === str
|
||||
scanadd str
|
||||
else
|
||||
add_text quote_phrase(str)
|
||||
end
|
||||
end
|
||||
|
||||
# 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
|
||||
elsif not CONTROL_CHAR === v
|
||||
add_text k + '=' + quote_token(v)
|
||||
else
|
||||
# apply RFC2231 encoding
|
||||
kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v)
|
||||
add_text kv
|
||||
end
|
||||
end
|
||||
|
||||
def encode_value( str )
|
||||
str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scanadd( str, force = false )
|
||||
types = ''
|
||||
strs = []
|
||||
if str.respond_to?(:encoding)
|
||||
enc = str.encoding
|
||||
str.force_encoding(Encoding::ASCII_8BIT)
|
||||
end
|
||||
until str.empty?
|
||||
if m = /\A[^\e\t\r\n ]+/.match(str)
|
||||
types << (force ? 'j' : 'a')
|
||||
if str.respond_to?(:encoding)
|
||||
strs.push m[0].force_encoding(enc)
|
||||
else
|
||||
strs.push m[0]
|
||||
end
|
||||
elsif m = /\A[\t\r\n ]+/.match(str)
|
||||
types << 's'
|
||||
if str.respond_to?(:encoding)
|
||||
strs.push m[0].force_encoding(enc)
|
||||
else
|
||||
strs.push m[0]
|
||||
end
|
||||
|
||||
elsif m = /\A\e../.match(str)
|
||||
esc = m[0]
|
||||
str = m.post_match
|
||||
if esc != "\e(B" and m = /\A[^\e]+/.match(str)
|
||||
types << 'j'
|
||||
if str.respond_to?(:encoding)
|
||||
strs.push m[0].force_encoding(enc)
|
||||
else
|
||||
strs.push m[0]
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
raise 'TMail FATAL: encoder scan fail'
|
||||
end
|
||||
(str = m.post_match) unless m.nil?
|
||||
end
|
||||
|
||||
do_encode types, strs
|
||||
end
|
||||
|
||||
def do_encode( types, strs )
|
||||
#
|
||||
# result : (A|E)(S(A|E))*
|
||||
# E : W(SW)*
|
||||
# W : (J|A)+ but must contain J # (J|A)*J(J|A)*
|
||||
# A : <<A character string not to be encoded>>
|
||||
# J : <<A character string to be encoded>>
|
||||
# S : <<LWSP>>
|
||||
#
|
||||
# An encoding unit is `E'.
|
||||
# Input (parameter `types') is (J|A)(J|A|S)*(J|A)
|
||||
#
|
||||
if BENCODE_DEBUG
|
||||
puts
|
||||
puts '-- do_encode ------------'
|
||||
puts types.split(//).join(' ')
|
||||
p strs
|
||||
end
|
||||
|
||||
e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/
|
||||
|
||||
while m = e.match(types)
|
||||
pre = m.pre_match
|
||||
concat_A_S pre, strs[0, pre.size] unless pre.empty?
|
||||
concat_E m[0], strs[m.begin(0) ... m.end(0)]
|
||||
types = m.post_match
|
||||
strs.slice! 0, m.end(0)
|
||||
end
|
||||
concat_A_S types, strs
|
||||
end
|
||||
|
||||
def concat_A_S( types, strs )
|
||||
if RUBY_VERSION < '1.9'
|
||||
a = ?a; s = ?s
|
||||
else
|
||||
a = 'a'.ord; s = 's'.ord
|
||||
end
|
||||
i = 0
|
||||
types.each_byte do |t|
|
||||
case t
|
||||
when a then add_text strs[i]
|
||||
when s then add_lwsp strs[i]
|
||||
else
|
||||
raise "TMail FATAL: unknown flag: #{t.chr}"
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
METHOD_ID = {
|
||||
?j => :extract_J,
|
||||
?e => :extract_E,
|
||||
?a => :extract_A,
|
||||
?s => :extract_S
|
||||
}
|
||||
|
||||
def concat_E( types, strs )
|
||||
if BENCODE_DEBUG
|
||||
puts '---- concat_E'
|
||||
puts "types=#{types.split(//).join(' ')}"
|
||||
puts "strs =#{strs.inspect}"
|
||||
end
|
||||
|
||||
flush() unless @text.empty?
|
||||
|
||||
chunk = ''
|
||||
strs.each_with_index do |s,i|
|
||||
mid = METHOD_ID[types[i]]
|
||||
until s.empty?
|
||||
unless c = __send__(mid, chunk.size, s)
|
||||
add_with_encode chunk unless chunk.empty?
|
||||
flush
|
||||
chunk = ''
|
||||
fold
|
||||
c = __send__(mid, 0, s)
|
||||
raise 'TMail FATAL: extract fail' unless c
|
||||
end
|
||||
chunk << c
|
||||
end
|
||||
end
|
||||
add_with_encode chunk unless chunk.empty?
|
||||
end
|
||||
|
||||
def extract_J( chunksize, str )
|
||||
size = max_bytes(chunksize, str.size) - 6
|
||||
size = (size % 2 == 0) ? (size) : (size - 1)
|
||||
return nil if size <= 0
|
||||
if str.respond_to?(:encoding)
|
||||
enc = str.encoding
|
||||
str.force_encoding(Encoding::ASCII_8BIT)
|
||||
"\e$B#{str.slice!(0, size)}\e(B".force_encoding(enc)
|
||||
else
|
||||
"\e$B#{str.slice!(0, size)}\e(B"
|
||||
end
|
||||
end
|
||||
|
||||
def extract_A( chunksize, str )
|
||||
size = max_bytes(chunksize, str.size)
|
||||
return nil if size <= 0
|
||||
str.slice!(0, size)
|
||||
end
|
||||
|
||||
alias extract_S extract_A
|
||||
|
||||
def max_bytes( chunksize, ssize )
|
||||
(restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize
|
||||
end
|
||||
|
||||
#
|
||||
# free length buffer
|
||||
#
|
||||
|
||||
def add_text( str )
|
||||
@text << str
|
||||
# puts '---- text -------------------------------------'
|
||||
# puts "+ #{str.inspect}"
|
||||
# puts "txt >>>#{@text.inspect}<<<"
|
||||
end
|
||||
|
||||
def add_with_encode( str )
|
||||
@text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
|
||||
end
|
||||
|
||||
def add_lwsp( lwsp )
|
||||
# puts '---- lwsp -------------------------------------'
|
||||
# puts "+ #{lwsp.inspect}"
|
||||
fold if restsize() <= 0
|
||||
flush(@folded)
|
||||
@lwsp = lwsp
|
||||
end
|
||||
|
||||
def flush(folded = false)
|
||||
# puts '---- flush ----'
|
||||
# puts "spc >>>#{@lwsp.inspect}<<<"
|
||||
# puts "txt >>>#{@text.inspect}<<<"
|
||||
@f << @lwsp << @text
|
||||
if folded
|
||||
@curlen = 0
|
||||
else
|
||||
@curlen += (@lwsp.size + @text.size)
|
||||
end
|
||||
@text = ''
|
||||
@lwsp = ''
|
||||
end
|
||||
|
||||
def fold
|
||||
# puts '---- fold ----'
|
||||
unless @f.string =~ /^.*?:$/
|
||||
@f << @eol
|
||||
@lwsp = SPACER
|
||||
else
|
||||
fold_header
|
||||
@folded = true
|
||||
end
|
||||
@curlen = 0
|
||||
end
|
||||
|
||||
def fold_header
|
||||
# Called because line is too long - so we need to wrap.
|
||||
# First look for whitespace in the text
|
||||
# if it has text, fold there
|
||||
# check the remaining text, if too long, fold again
|
||||
# if it doesn't, then don't fold unless the line goes beyond 998 chars
|
||||
|
||||
# Check the text to see if there is whitespace, or if not
|
||||
@wrapped_text = []
|
||||
until @text.blank?
|
||||
fold_the_string
|
||||
end
|
||||
@text = @wrapped_text.join("#{@eol}#{SPACER}")
|
||||
end
|
||||
|
||||
def fold_the_string
|
||||
whitespace_location = @text =~ /\s/ || @text.length
|
||||
# Is the location of the whitespace shorter than the RCF_2822_MAX_LENGTH?
|
||||
# if there is no whitespace in the string, then this
|
||||
unless mazsize(whitespace_location) <= 0
|
||||
@text.strip!
|
||||
@wrapped_text << @text.slice!(0...whitespace_location)
|
||||
# If it is not less, we have to wrap it destructively
|
||||
else
|
||||
slice_point = RFC_2822_MAX_LENGTH - @curlen - @lwsp.length
|
||||
@text.strip!
|
||||
@wrapped_text << @text.slice!(0...slice_point)
|
||||
end
|
||||
end
|
||||
|
||||
def restsize
|
||||
MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size)
|
||||
end
|
||||
|
||||
def mazsize(whitespace_location)
|
||||
# Per RFC2822, the maximum length of a line is 998 chars
|
||||
RFC_2822_MAX_LENGTH - (@curlen + @lwsp.size + whitespace_location)
|
||||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
end # module TMail
|
||||
@@ -1,960 +0,0 @@
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/encode'
|
||||
require 'tmail/address'
|
||||
require 'tmail/parser'
|
||||
require 'tmail/config'
|
||||
require 'tmail/utils'
|
||||
|
||||
#:startdoc:
|
||||
module TMail
|
||||
|
||||
# Provides methods to handle and manipulate headers in the email
|
||||
class HeaderField
|
||||
|
||||
include TextUtils
|
||||
|
||||
class << self
|
||||
|
||||
alias newobj new
|
||||
|
||||
def new( name, body, conf = DEFAULT_CONFIG )
|
||||
klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
|
||||
klass.newobj body, conf
|
||||
end
|
||||
|
||||
# Returns a HeaderField object matching the header you specify in the "name" param.
|
||||
# Requires an initialized TMail::Port to be passed in.
|
||||
#
|
||||
# The method searches the header of the Port you pass into it to find a match on
|
||||
# the header line you pass. Once a match is found, it will unwrap the matching line
|
||||
# as needed to return an initialized HeaderField object.
|
||||
#
|
||||
# If you want to get the Envelope sender of the email object, pass in "EnvelopeSender",
|
||||
# if you want the From address of the email itself, pass in 'From'.
|
||||
#
|
||||
# This is because a mailbox doesn't have the : after the From that designates the
|
||||
# beginning of the envelope sender (which can be different to the from address of
|
||||
# the email)
|
||||
#
|
||||
# Other fields can be passed as normal, "Reply-To", "Received" etc.
|
||||
#
|
||||
# Note: Change of behaviour in 1.2.1 => returns nil if it does not find the specified
|
||||
# header field, otherwise returns an instantiated object of the correct header class
|
||||
#
|
||||
# For example:
|
||||
# port = TMail::FilePort.new("/test/fixtures/raw_email_simple")
|
||||
# h = TMail::HeaderField.new_from_port(port, "From")
|
||||
# h.addrs.to_s #=> "Mikel Lindsaar <mikel@nowhere.com>"
|
||||
# h = TMail::HeaderField.new_from_port(port, "EvelopeSender")
|
||||
# h.addrs.to_s #=> "mike@anotherplace.com.au"
|
||||
# h = TMail::HeaderField.new_from_port(port, "SomeWeirdHeaderField")
|
||||
# h #=> nil
|
||||
def new_from_port( port, name, conf = DEFAULT_CONFIG )
|
||||
if name == "EnvelopeSender"
|
||||
name = "From"
|
||||
re = Regexp.new('\A(From) ', 'i')
|
||||
else
|
||||
re = Regexp.new('\A(' + Regexp.quote(name) + '):', 'i')
|
||||
end
|
||||
str = nil
|
||||
port.ropen {|f|
|
||||
f.each do |line|
|
||||
if m = re.match(line) then str = m.post_match.strip
|
||||
elsif str and /\A[\t ]/ === line then str << ' ' << line.strip
|
||||
elsif /\A-*\s*\z/ === line then break
|
||||
elsif str then break
|
||||
end
|
||||
end
|
||||
}
|
||||
new(name, str, Config.to_config(conf)) if str
|
||||
end
|
||||
|
||||
def internal_new( name, conf )
|
||||
FNAME_TO_CLASS[name].newobj('', conf, true)
|
||||
end
|
||||
|
||||
end # class << self
|
||||
|
||||
def initialize( body, conf, intern = false )
|
||||
@body = body
|
||||
@config = conf
|
||||
|
||||
@illegal = false
|
||||
@parsed = false
|
||||
|
||||
if intern
|
||||
@parsed = true
|
||||
parse_init
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{@body.inspect}>"
|
||||
end
|
||||
|
||||
def illegal?
|
||||
@illegal
|
||||
end
|
||||
|
||||
def empty?
|
||||
ensure_parsed
|
||||
return true if @illegal
|
||||
isempty?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_parsed
|
||||
return if @parsed
|
||||
@parsed = true
|
||||
parse
|
||||
end
|
||||
|
||||
# defabstract parse
|
||||
# end
|
||||
|
||||
def clear_parse_status
|
||||
@parsed = false
|
||||
@illegal = false
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
def body
|
||||
ensure_parsed
|
||||
v = Decoder.new(s = '')
|
||||
do_accept v
|
||||
v.terminate
|
||||
s
|
||||
end
|
||||
|
||||
def body=( str )
|
||||
@body = str
|
||||
clear_parse_status
|
||||
end
|
||||
|
||||
include StrategyInterface
|
||||
|
||||
def accept( strategy )
|
||||
ensure_parsed
|
||||
do_accept strategy
|
||||
strategy.terminate
|
||||
end
|
||||
|
||||
# abstract do_accept
|
||||
|
||||
end
|
||||
|
||||
|
||||
class UnstructuredHeader < HeaderField
|
||||
|
||||
def body
|
||||
ensure_parsed
|
||||
@body
|
||||
end
|
||||
|
||||
def body=( arg )
|
||||
ensure_parsed
|
||||
@body = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_init
|
||||
end
|
||||
|
||||
def parse
|
||||
@body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not @body
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.text @body
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class StructuredHeader < HeaderField
|
||||
|
||||
def comments
|
||||
ensure_parsed
|
||||
if @comments[0]
|
||||
[Decoder.decode(@comments[0])]
|
||||
else
|
||||
@comments
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse
|
||||
save = nil
|
||||
|
||||
begin
|
||||
parse_init
|
||||
do_parse
|
||||
rescue SyntaxError
|
||||
if not save and mime_encoded? @body
|
||||
save = @body
|
||||
@body = Decoder.decode(save)
|
||||
retry
|
||||
elsif save
|
||||
@body = save
|
||||
end
|
||||
|
||||
@illegal = true
|
||||
raise if @config.strict_parse?
|
||||
end
|
||||
end
|
||||
|
||||
def parse_init
|
||||
@comments = []
|
||||
init
|
||||
end
|
||||
|
||||
def do_parse
|
||||
quote_boundary
|
||||
obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
|
||||
set obj if obj
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class DateTimeHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :DATETIME
|
||||
|
||||
def date
|
||||
ensure_parsed
|
||||
@date
|
||||
end
|
||||
|
||||
def date=( arg )
|
||||
ensure_parsed
|
||||
@date = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@date = nil
|
||||
end
|
||||
|
||||
def set( t )
|
||||
@date = t
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not @date
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.meta time2str(@date)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class AddressHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :MADDRESS
|
||||
|
||||
def addrs
|
||||
ensure_parsed
|
||||
@addrs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@addrs = []
|
||||
end
|
||||
|
||||
def set( a )
|
||||
@addrs = a
|
||||
end
|
||||
|
||||
def isempty?
|
||||
@addrs.empty?
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
first = true
|
||||
@addrs.each do |a|
|
||||
if first
|
||||
first = false
|
||||
else
|
||||
strategy.meta ','
|
||||
strategy.space
|
||||
end
|
||||
a.accept strategy
|
||||
end
|
||||
|
||||
@comments.each do |c|
|
||||
strategy.space
|
||||
strategy.meta '('
|
||||
strategy.text c
|
||||
strategy.meta ')'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ReturnPathHeader < AddressHeader
|
||||
|
||||
PARSE_TYPE = :RETPATH
|
||||
|
||||
def addr
|
||||
addrs()[0]
|
||||
end
|
||||
|
||||
def spec
|
||||
a = addr() or return nil
|
||||
a.spec
|
||||
end
|
||||
|
||||
def routes
|
||||
a = addr() or return nil
|
||||
a.routes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def do_accept( strategy )
|
||||
a = addr()
|
||||
|
||||
strategy.meta '<'
|
||||
unless a.routes.empty?
|
||||
strategy.meta a.routes.map {|i| '@' + i }.join(',')
|
||||
strategy.meta ':'
|
||||
end
|
||||
spec = a.spec
|
||||
strategy.meta spec if spec
|
||||
strategy.meta '>'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class SingleAddressHeader < AddressHeader
|
||||
|
||||
def addr
|
||||
addrs()[0]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def do_accept( strategy )
|
||||
a = addr()
|
||||
a.accept strategy
|
||||
@comments.each do |c|
|
||||
strategy.space
|
||||
strategy.meta '('
|
||||
strategy.text c
|
||||
strategy.meta ')'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class MessageIdHeader < StructuredHeader
|
||||
|
||||
def id
|
||||
ensure_parsed
|
||||
@id
|
||||
end
|
||||
|
||||
def id=( arg )
|
||||
ensure_parsed
|
||||
@id = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@id = nil
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not @id
|
||||
end
|
||||
|
||||
def do_parse
|
||||
@id = @body.slice(MESSAGE_ID) or
|
||||
raise SyntaxError, "wrong Message-ID format: #{@body}"
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.meta @id
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ReferencesHeader < StructuredHeader
|
||||
|
||||
def refs
|
||||
ensure_parsed
|
||||
@refs
|
||||
end
|
||||
|
||||
def each_id
|
||||
self.refs.each do |i|
|
||||
yield i if MESSAGE_ID === i
|
||||
end
|
||||
end
|
||||
|
||||
def ids
|
||||
ensure_parsed
|
||||
@ids
|
||||
end
|
||||
|
||||
def each_phrase
|
||||
self.refs.each do |i|
|
||||
yield i unless MESSAGE_ID === i
|
||||
end
|
||||
end
|
||||
|
||||
def phrases
|
||||
ret = []
|
||||
each_phrase {|i| ret.push i }
|
||||
ret
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@refs = []
|
||||
@ids = []
|
||||
end
|
||||
|
||||
def isempty?
|
||||
@ids.empty?
|
||||
end
|
||||
|
||||
def do_parse
|
||||
str = @body
|
||||
while m = MESSAGE_ID.match(str)
|
||||
pre = m.pre_match.strip
|
||||
@refs.push pre unless pre.empty?
|
||||
@refs.push s = m[0]
|
||||
@ids.push s
|
||||
str = m.post_match
|
||||
end
|
||||
str = str.strip
|
||||
@refs.push str unless str.empty?
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
first = true
|
||||
@ids.each do |i|
|
||||
if first
|
||||
first = false
|
||||
else
|
||||
strategy.space
|
||||
end
|
||||
strategy.meta i
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ReceivedHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :RECEIVED
|
||||
|
||||
def from
|
||||
ensure_parsed
|
||||
@from
|
||||
end
|
||||
|
||||
def from=( arg )
|
||||
ensure_parsed
|
||||
@from = arg
|
||||
end
|
||||
|
||||
def by
|
||||
ensure_parsed
|
||||
@by
|
||||
end
|
||||
|
||||
def by=( arg )
|
||||
ensure_parsed
|
||||
@by = arg
|
||||
end
|
||||
|
||||
def via
|
||||
ensure_parsed
|
||||
@via
|
||||
end
|
||||
|
||||
def via=( arg )
|
||||
ensure_parsed
|
||||
@via = arg
|
||||
end
|
||||
|
||||
def with
|
||||
ensure_parsed
|
||||
@with
|
||||
end
|
||||
|
||||
def id
|
||||
ensure_parsed
|
||||
@id
|
||||
end
|
||||
|
||||
def id=( arg )
|
||||
ensure_parsed
|
||||
@id = arg
|
||||
end
|
||||
|
||||
def _for
|
||||
ensure_parsed
|
||||
@_for
|
||||
end
|
||||
|
||||
def _for=( arg )
|
||||
ensure_parsed
|
||||
@_for = arg
|
||||
end
|
||||
|
||||
def date
|
||||
ensure_parsed
|
||||
@date
|
||||
end
|
||||
|
||||
def date=( arg )
|
||||
ensure_parsed
|
||||
@date = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@from = @by = @via = @with = @id = @_for = nil
|
||||
@with = []
|
||||
@date = nil
|
||||
end
|
||||
|
||||
def set( args )
|
||||
@from, @by, @via, @with, @id, @_for, @date = *args
|
||||
end
|
||||
|
||||
def isempty?
|
||||
@with.empty? and not (@from or @by or @via or @id or @_for or @date)
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
list = []
|
||||
list.push 'from ' + @from if @from
|
||||
list.push 'by ' + @by if @by
|
||||
list.push 'via ' + @via if @via
|
||||
@with.each do |i|
|
||||
list.push 'with ' + i
|
||||
end
|
||||
list.push 'id ' + @id if @id
|
||||
list.push 'for <' + @_for + '>' if @_for
|
||||
|
||||
first = true
|
||||
list.each do |i|
|
||||
strategy.space unless first
|
||||
strategy.meta i
|
||||
first = false
|
||||
end
|
||||
if @date
|
||||
strategy.meta ';'
|
||||
strategy.space
|
||||
strategy.meta time2str(@date)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class KeywordsHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :KEYWORDS
|
||||
|
||||
def keys
|
||||
ensure_parsed
|
||||
@keys
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@keys = []
|
||||
end
|
||||
|
||||
def set( a )
|
||||
@keys = a
|
||||
end
|
||||
|
||||
def isempty?
|
||||
@keys.empty?
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
first = true
|
||||
@keys.each do |i|
|
||||
if first
|
||||
first = false
|
||||
else
|
||||
strategy.meta ','
|
||||
end
|
||||
strategy.meta i
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class EncryptedHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :ENCRYPTED
|
||||
|
||||
def encrypter
|
||||
ensure_parsed
|
||||
@encrypter
|
||||
end
|
||||
|
||||
def encrypter=( arg )
|
||||
ensure_parsed
|
||||
@encrypter = arg
|
||||
end
|
||||
|
||||
def keyword
|
||||
ensure_parsed
|
||||
@keyword
|
||||
end
|
||||
|
||||
def keyword=( arg )
|
||||
ensure_parsed
|
||||
@keyword = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@encrypter = nil
|
||||
@keyword = nil
|
||||
end
|
||||
|
||||
def set( args )
|
||||
@encrypter, @keyword = args
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not (@encrypter or @keyword)
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
if @key
|
||||
strategy.meta @encrypter + ','
|
||||
strategy.space
|
||||
strategy.meta @keyword
|
||||
else
|
||||
strategy.meta @encrypter
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class MimeVersionHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :MIMEVERSION
|
||||
|
||||
def major
|
||||
ensure_parsed
|
||||
@major
|
||||
end
|
||||
|
||||
def major=( arg )
|
||||
ensure_parsed
|
||||
@major = arg
|
||||
end
|
||||
|
||||
def minor
|
||||
ensure_parsed
|
||||
@minor
|
||||
end
|
||||
|
||||
def minor=( arg )
|
||||
ensure_parsed
|
||||
@minor = arg
|
||||
end
|
||||
|
||||
def version
|
||||
sprintf('%d.%d', major, minor)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@major = nil
|
||||
@minor = nil
|
||||
end
|
||||
|
||||
def set( args )
|
||||
@major, @minor = *args
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not (@major or @minor)
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.meta sprintf('%d.%d', @major, @minor)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ContentTypeHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :CTYPE
|
||||
|
||||
def main_type
|
||||
ensure_parsed
|
||||
@main
|
||||
end
|
||||
|
||||
def main_type=( arg )
|
||||
ensure_parsed
|
||||
@main = arg.downcase
|
||||
end
|
||||
|
||||
def sub_type
|
||||
ensure_parsed
|
||||
@sub
|
||||
end
|
||||
|
||||
def sub_type=( arg )
|
||||
ensure_parsed
|
||||
@sub = arg.downcase
|
||||
end
|
||||
|
||||
def content_type
|
||||
ensure_parsed
|
||||
@sub ? sprintf('%s/%s', @main, @sub) : @main
|
||||
end
|
||||
|
||||
def params
|
||||
ensure_parsed
|
||||
unless @params.blank?
|
||||
@params.each do |k, v|
|
||||
@params[k] = unquote(v)
|
||||
end
|
||||
end
|
||||
@params
|
||||
end
|
||||
|
||||
def []( key )
|
||||
ensure_parsed
|
||||
@params and unquote(@params[key])
|
||||
end
|
||||
|
||||
def []=( key, val )
|
||||
ensure_parsed
|
||||
(@params ||= {})[key] = val
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@main = @sub = @params = nil
|
||||
end
|
||||
|
||||
def set( args )
|
||||
@main, @sub, @params = *args
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not (@main or @sub)
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
if @sub
|
||||
strategy.meta sprintf('%s/%s', @main, @sub)
|
||||
else
|
||||
strategy.meta @main
|
||||
end
|
||||
@params.each do |k,v|
|
||||
if v
|
||||
strategy.meta ';'
|
||||
strategy.space
|
||||
strategy.kv_pair k, v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ContentTransferEncodingHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :CENCODING
|
||||
|
||||
def encoding
|
||||
ensure_parsed
|
||||
@encoding
|
||||
end
|
||||
|
||||
def encoding=( arg )
|
||||
ensure_parsed
|
||||
@encoding = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@encoding = nil
|
||||
end
|
||||
|
||||
def set( s )
|
||||
@encoding = s
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not @encoding
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.meta @encoding.capitalize
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ContentDispositionHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :CDISPOSITION
|
||||
|
||||
def disposition
|
||||
ensure_parsed
|
||||
@disposition
|
||||
end
|
||||
|
||||
def disposition=( str )
|
||||
ensure_parsed
|
||||
@disposition = str.downcase
|
||||
end
|
||||
|
||||
def params
|
||||
ensure_parsed
|
||||
unless @params.blank?
|
||||
@params.each do |k, v|
|
||||
@params[k] = unquote(v)
|
||||
end
|
||||
end
|
||||
@params
|
||||
end
|
||||
|
||||
def []( key )
|
||||
ensure_parsed
|
||||
@params and unquote(@params[key])
|
||||
end
|
||||
|
||||
def []=( key, val )
|
||||
ensure_parsed
|
||||
(@params ||= {})[key] = val
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@disposition = @params = nil
|
||||
end
|
||||
|
||||
def set( args )
|
||||
@disposition, @params = *args
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not @disposition and (not @params or @params.empty?)
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.meta @disposition
|
||||
@params.each do |k,v|
|
||||
strategy.meta ';'
|
||||
strategy.space
|
||||
strategy.kv_pair k, unquote(v)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class HeaderField # redefine
|
||||
|
||||
FNAME_TO_CLASS = {
|
||||
'date' => DateTimeHeader,
|
||||
'resent-date' => DateTimeHeader,
|
||||
'to' => AddressHeader,
|
||||
'cc' => AddressHeader,
|
||||
'bcc' => AddressHeader,
|
||||
'from' => AddressHeader,
|
||||
'reply-to' => AddressHeader,
|
||||
'resent-to' => AddressHeader,
|
||||
'resent-cc' => AddressHeader,
|
||||
'resent-bcc' => AddressHeader,
|
||||
'resent-from' => AddressHeader,
|
||||
'resent-reply-to' => AddressHeader,
|
||||
'sender' => SingleAddressHeader,
|
||||
'resent-sender' => SingleAddressHeader,
|
||||
'return-path' => ReturnPathHeader,
|
||||
'message-id' => MessageIdHeader,
|
||||
'resent-message-id' => MessageIdHeader,
|
||||
'in-reply-to' => ReferencesHeader,
|
||||
'received' => ReceivedHeader,
|
||||
'references' => ReferencesHeader,
|
||||
'keywords' => KeywordsHeader,
|
||||
'encrypted' => EncryptedHeader,
|
||||
'mime-version' => MimeVersionHeader,
|
||||
'content-type' => ContentTypeHeader,
|
||||
'content-transfer-encoding' => ContentTransferEncodingHeader,
|
||||
'content-disposition' => ContentDispositionHeader,
|
||||
'content-id' => MessageIdHeader,
|
||||
'subject' => UnstructuredHeader,
|
||||
'comments' => UnstructuredHeader,
|
||||
'content-description' => UnstructuredHeader
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
@@ -1,9 +0,0 @@
|
||||
#:stopdoc:
|
||||
# This is here for Rolls.
|
||||
# Rolls uses this instead of lib/tmail.rb.
|
||||
|
||||
require 'tmail/version'
|
||||
require 'tmail/mail'
|
||||
require 'tmail/mailbox'
|
||||
require 'tmail/core_extensions'
|
||||
#:startdoc:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
#:stopdoc:
|
||||
require 'tmail/mailbox'
|
||||
#:startdoc:
|
||||
@@ -1,579 +0,0 @@
|
||||
=begin rdoc
|
||||
|
||||
= Mail class
|
||||
|
||||
=end
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
|
||||
|
||||
require 'tmail/interface'
|
||||
require 'tmail/encode'
|
||||
require 'tmail/header'
|
||||
require 'tmail/port'
|
||||
require 'tmail/config'
|
||||
require 'tmail/utils'
|
||||
require 'tmail/attachments'
|
||||
require 'tmail/quoting'
|
||||
require 'socket'
|
||||
|
||||
module TMail
|
||||
|
||||
# == Mail Class
|
||||
#
|
||||
# Accessing a TMail object done via the TMail::Mail class. As email can be fairly complex
|
||||
# creatures, you will find a large amount of accessor and setter methods in this class!
|
||||
#
|
||||
# Most of the below methods handle the header, in fact, what TMail does best is handle the
|
||||
# header of the email object. There are only a few methods that deal directly with the body
|
||||
# of the email, such as base64_encode and base64_decode.
|
||||
#
|
||||
# === Using TMail inside your code
|
||||
#
|
||||
# The usual way is to install the gem (see the {README}[link:/README] on how to do this) and
|
||||
# then put at the top of your class:
|
||||
#
|
||||
# require 'tmail'
|
||||
#
|
||||
# You can then create a new TMail object in your code with:
|
||||
#
|
||||
# @email = TMail::Mail.new
|
||||
#
|
||||
# Or if you have an email as a string, you can initialize a new TMail::Mail object and get it
|
||||
# to parse that string for you like so:
|
||||
#
|
||||
# @email = TMail::Mail.parse(email_text)
|
||||
#
|
||||
# You can also read a single email off the disk, for example:
|
||||
#
|
||||
# @email = TMail::Mail.load('filename.txt')
|
||||
#
|
||||
# Also, you can read a mailbox (usual unix mbox format) and end up with an array of TMail
|
||||
# objects by doing something like this:
|
||||
#
|
||||
# # Note, we pass true as the last variable to open the mailbox read only
|
||||
# mailbox = TMail::UNIXMbox.new("mailbox", nil, true)
|
||||
# @emails = []
|
||||
# mailbox.each_port { |m| @emails << TMail::Mail.new(m) }
|
||||
#
|
||||
class Mail
|
||||
|
||||
class << self
|
||||
|
||||
# Opens an email that has been saved out as a file by itself.
|
||||
#
|
||||
# This function will read a file non-destructively and then parse
|
||||
# the contents and return a TMail::Mail object.
|
||||
#
|
||||
# Does not handle multiple email mailboxes (like a unix mbox) for that
|
||||
# use the TMail::UNIXMbox class.
|
||||
#
|
||||
# Example:
|
||||
# mail = TMail::Mail.load('filename')
|
||||
#
|
||||
def load( fname )
|
||||
new(FilePort.new(fname))
|
||||
end
|
||||
|
||||
alias load_from load
|
||||
alias loadfrom load
|
||||
|
||||
# Parses an email from the supplied string and returns a TMail::Mail
|
||||
# object.
|
||||
#
|
||||
# Example:
|
||||
# require 'rubygems'; require 'tmail'
|
||||
# email_string =<<HEREDOC
|
||||
# To: mikel@lindsaar.net
|
||||
# From: mikel@me.com
|
||||
# Subject: This is a short Email
|
||||
#
|
||||
# Hello there Mikel!
|
||||
#
|
||||
# HEREDOC
|
||||
# mail = TMail::Mail.parse(email_string)
|
||||
# #=> #<TMail::Mail port=#<TMail::StringPort:id=0xa30ac0> bodyport=nil>
|
||||
# mail.body
|
||||
# #=> "Hello there Mikel!\n\n"
|
||||
def parse( str )
|
||||
new(StringPort.new(str))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def initialize( port = nil, conf = DEFAULT_CONFIG ) #:nodoc:
|
||||
@port = port || StringPort.new
|
||||
@config = Config.to_config(conf)
|
||||
|
||||
@header = {}
|
||||
@body_port = nil
|
||||
@body_parsed = false
|
||||
@epilogue = ''
|
||||
@parts = []
|
||||
|
||||
@port.ropen {|f|
|
||||
parse_header f
|
||||
parse_body f unless @port.reproducible?
|
||||
}
|
||||
end
|
||||
|
||||
# Provides access to the port this email is using to hold it's data
|
||||
#
|
||||
# Example:
|
||||
# mail = TMail::Mail.parse(email_string)
|
||||
# mail.port
|
||||
# #=> #<TMail::StringPort:id=0xa2c952>
|
||||
attr_reader :port
|
||||
|
||||
def inspect
|
||||
"\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>"
|
||||
end
|
||||
|
||||
#
|
||||
# to_s interfaces
|
||||
#
|
||||
|
||||
public
|
||||
|
||||
include StrategyInterface
|
||||
|
||||
def write_back( eol = "\n", charset = 'e' )
|
||||
parse_body
|
||||
@port.wopen {|stream| encoded eol, charset, stream }
|
||||
end
|
||||
|
||||
def accept( strategy )
|
||||
with_multipart_encoding(strategy) {
|
||||
ordered_each do |name, field|
|
||||
next if field.empty?
|
||||
strategy.header_name canonical(name)
|
||||
field.accept strategy
|
||||
strategy.puts
|
||||
end
|
||||
strategy.puts
|
||||
body_port().ropen {|r|
|
||||
strategy.write r.read
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def canonical( name )
|
||||
name.split(/-/).map {|s| s.capitalize }.join('-')
|
||||
end
|
||||
|
||||
def with_multipart_encoding( strategy )
|
||||
if parts().empty? # DO NOT USE @parts
|
||||
yield
|
||||
|
||||
else
|
||||
bound = ::TMail.new_boundary
|
||||
if @header.key? 'content-type'
|
||||
@header['content-type'].params['boundary'] = bound
|
||||
else
|
||||
store 'Content-Type', %<multipart/mixed; boundary="#{bound}">
|
||||
end
|
||||
|
||||
yield
|
||||
|
||||
parts().each do |tm|
|
||||
strategy.puts
|
||||
strategy.puts '--' + bound
|
||||
tm.accept strategy
|
||||
end
|
||||
strategy.puts
|
||||
strategy.puts '--' + bound + '--'
|
||||
strategy.write epilogue()
|
||||
end
|
||||
end
|
||||
|
||||
###
|
||||
### header
|
||||
###
|
||||
|
||||
public
|
||||
|
||||
ALLOW_MULTIPLE = {
|
||||
'received' => true,
|
||||
'resent-date' => true,
|
||||
'resent-from' => true,
|
||||
'resent-sender' => true,
|
||||
'resent-to' => true,
|
||||
'resent-cc' => true,
|
||||
'resent-bcc' => true,
|
||||
'resent-message-id' => true,
|
||||
'comments' => true,
|
||||
'keywords' => true
|
||||
}
|
||||
USE_ARRAY = ALLOW_MULTIPLE
|
||||
|
||||
def header
|
||||
@header.dup
|
||||
end
|
||||
|
||||
# Returns a TMail::AddressHeader object of the field you are querying.
|
||||
# Examples:
|
||||
# @mail['from'] #=> #<TMail::AddressHeader "mikel@test.com.au">
|
||||
# @mail['to'] #=> #<TMail::AddressHeader "mikel@test.com.au">
|
||||
#
|
||||
# You can get the string value of this by passing "to_s" to the query:
|
||||
# Example:
|
||||
# @mail['to'].to_s #=> "mikel@test.com.au"
|
||||
def []( key )
|
||||
@header[key.downcase]
|
||||
end
|
||||
|
||||
def sub_header(key, param)
|
||||
(hdr = self[key]) ? hdr[param] : nil
|
||||
end
|
||||
|
||||
alias fetch []
|
||||
|
||||
# Allows you to set or delete TMail header objects at will.
|
||||
# Examples:
|
||||
# @mail = TMail::Mail.new
|
||||
# @mail['to'].to_s # => 'mikel@test.com.au'
|
||||
# @mail['to'] = 'mikel@elsewhere.org'
|
||||
# @mail['to'].to_s # => 'mikel@elsewhere.org'
|
||||
# @mail.encoded # => "To: mikel@elsewhere.org\r\n\r\n"
|
||||
# @mail['to'] = nil
|
||||
# @mail['to'].to_s # => nil
|
||||
# @mail.encoded # => "\r\n"
|
||||
#
|
||||
# Note: setting mail[] = nil actually deletes the header field in question from the object,
|
||||
# it does not just set the value of the hash to nil
|
||||
def []=( key, val )
|
||||
dkey = key.downcase
|
||||
|
||||
if val.nil?
|
||||
@header.delete dkey
|
||||
return nil
|
||||
end
|
||||
|
||||
case val
|
||||
when String
|
||||
header = new_hf(key, val)
|
||||
when HeaderField
|
||||
;
|
||||
when Array
|
||||
ALLOW_MULTIPLE.include? dkey or
|
||||
raise ArgumentError, "#{key}: Header must not be multiple"
|
||||
@header[dkey] = val
|
||||
return val
|
||||
else
|
||||
header = new_hf(key, val.to_s)
|
||||
end
|
||||
if ALLOW_MULTIPLE.include? dkey
|
||||
(@header[dkey] ||= []).push header
|
||||
else
|
||||
@header[dkey] = header
|
||||
end
|
||||
|
||||
val
|
||||
end
|
||||
|
||||
alias store []=
|
||||
|
||||
# Allows you to loop through each header in the TMail::Mail object in a block
|
||||
# Example:
|
||||
# @mail['to'] = 'mikel@elsewhere.org'
|
||||
# @mail['from'] = 'me@me.com'
|
||||
# @mail.each_header { |k,v| puts "#{k} = #{v}" }
|
||||
# # => from = me@me.com
|
||||
# # => to = mikel@elsewhere.org
|
||||
def each_header
|
||||
@header.each do |key, val|
|
||||
[val].flatten.each {|v| yield key, v }
|
||||
end
|
||||
end
|
||||
|
||||
alias each_pair each_header
|
||||
|
||||
def each_header_name( &block )
|
||||
@header.each_key(&block)
|
||||
end
|
||||
|
||||
alias each_key each_header_name
|
||||
|
||||
def each_field( &block )
|
||||
@header.values.flatten.each(&block)
|
||||
end
|
||||
|
||||
alias each_value each_field
|
||||
|
||||
FIELD_ORDER = %w(
|
||||
return-path received
|
||||
resent-date resent-from resent-sender resent-to
|
||||
resent-cc resent-bcc resent-message-id
|
||||
date from sender reply-to to cc bcc
|
||||
message-id in-reply-to references
|
||||
subject comments keywords
|
||||
mime-version content-type content-transfer-encoding
|
||||
content-disposition content-description
|
||||
)
|
||||
|
||||
def ordered_each
|
||||
list = @header.keys
|
||||
FIELD_ORDER.each do |name|
|
||||
if list.delete(name)
|
||||
[@header[name]].flatten.each {|v| yield name, v }
|
||||
end
|
||||
end
|
||||
list.each do |name|
|
||||
[@header[name]].flatten.each {|v| yield name, v }
|
||||
end
|
||||
end
|
||||
|
||||
def clear
|
||||
@header.clear
|
||||
end
|
||||
|
||||
def delete( key )
|
||||
@header.delete key.downcase
|
||||
end
|
||||
|
||||
def delete_if
|
||||
@header.delete_if do |key,val|
|
||||
if Array === val
|
||||
val.delete_if {|v| yield key, v }
|
||||
val.empty?
|
||||
else
|
||||
yield key, val
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def keys
|
||||
@header.keys
|
||||
end
|
||||
|
||||
def key?( key )
|
||||
@header.key? key.downcase
|
||||
end
|
||||
|
||||
def values_at( *args )
|
||||
args.map {|k| @header[k.downcase] }.flatten
|
||||
end
|
||||
|
||||
alias indexes values_at
|
||||
alias indices values_at
|
||||
|
||||
private
|
||||
|
||||
def parse_header( f )
|
||||
name = field = nil
|
||||
unixfrom = nil
|
||||
|
||||
while line = f.gets
|
||||
case line
|
||||
when /\A[ \t]/ # continue from prev line
|
||||
raise SyntaxError, 'mail is began by space' unless field
|
||||
field << ' ' << line.strip
|
||||
|
||||
when /\A([^\: \t]+):\s*/ # new header line
|
||||
add_hf name, field if field
|
||||
name = $1
|
||||
field = $' #.strip
|
||||
|
||||
when /\A\-*\s*\z/ # end of header
|
||||
add_hf name, field if field
|
||||
name = field = nil
|
||||
break
|
||||
|
||||
when /\AFrom (\S+)/
|
||||
unixfrom = $1
|
||||
|
||||
when /^charset=.*/
|
||||
|
||||
else
|
||||
raise SyntaxError, "wrong mail header: '#{line.inspect}'"
|
||||
end
|
||||
end
|
||||
add_hf name, field if name
|
||||
|
||||
if unixfrom
|
||||
add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path']
|
||||
end
|
||||
end
|
||||
|
||||
def add_hf( name, field )
|
||||
key = name.downcase
|
||||
field = new_hf(name, field)
|
||||
|
||||
if ALLOW_MULTIPLE.include? key
|
||||
(@header[key] ||= []).push field
|
||||
else
|
||||
@header[key] = field
|
||||
end
|
||||
end
|
||||
|
||||
def new_hf( name, field )
|
||||
HeaderField.new(name, field, @config)
|
||||
end
|
||||
|
||||
###
|
||||
### body
|
||||
###
|
||||
|
||||
public
|
||||
|
||||
def body_port
|
||||
parse_body
|
||||
@body_port
|
||||
end
|
||||
|
||||
def each( &block )
|
||||
body_port().ropen {|f| f.each(&block) }
|
||||
end
|
||||
|
||||
def quoted_body
|
||||
body_port.ropen {|f| return f.read }
|
||||
end
|
||||
|
||||
def quoted_body= str
|
||||
body_port.wopen { |f| f.write str }
|
||||
str
|
||||
end
|
||||
|
||||
def body=( str )
|
||||
# Sets the body of the email to a new (encoded) string.
|
||||
#
|
||||
# We also reparses the email if the body is ever reassigned, this is a performance hit, however when
|
||||
# you assign the body, you usually want to be able to make sure that you can access the attachments etc.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# mail.body = "Hello, this is\nthe body text"
|
||||
# # => "Hello, this is\nthe body"
|
||||
# mail.body
|
||||
# # => "Hello, this is\nthe body"
|
||||
@body_parsed = false
|
||||
parse_body(StringInput.new(str))
|
||||
parse_body
|
||||
@body_port.wopen {|f| f.write str }
|
||||
str
|
||||
end
|
||||
|
||||
alias preamble quoted_body
|
||||
alias preamble= quoted_body=
|
||||
|
||||
def epilogue
|
||||
parse_body
|
||||
@epilogue.dup
|
||||
end
|
||||
|
||||
def epilogue=( str )
|
||||
parse_body
|
||||
@epilogue = str
|
||||
str
|
||||
end
|
||||
|
||||
def parts
|
||||
parse_body
|
||||
@parts
|
||||
end
|
||||
|
||||
def each_part( &block )
|
||||
parts().each(&block)
|
||||
end
|
||||
|
||||
# Returns true if the content type of this part of the email is
|
||||
# a disposition attachment
|
||||
def disposition_is_attachment?
|
||||
(self['content-disposition'] && self['content-disposition'].disposition == "attachment")
|
||||
end
|
||||
|
||||
# Returns true if this part's content main type is text, else returns false.
|
||||
# By main type is meant "text/plain" is text. "text/html" is text
|
||||
def content_type_is_text?
|
||||
self.header['content-type'] && (self.header['content-type'].main_type != "text")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_body( f = nil )
|
||||
return if @body_parsed
|
||||
|
||||
if f
|
||||
parse_body_0 f
|
||||
else
|
||||
@port.ropen {|f|
|
||||
skip_header f
|
||||
parse_body_0 f
|
||||
}
|
||||
end
|
||||
@body_parsed = true
|
||||
end
|
||||
|
||||
def skip_header( f )
|
||||
while line = f.gets
|
||||
return if /\A[\r\n]*\z/ === line
|
||||
end
|
||||
end
|
||||
|
||||
def parse_body_0( f )
|
||||
if multipart?
|
||||
read_multipart f
|
||||
else
|
||||
@body_port = @config.new_body_port(self)
|
||||
@body_port.wopen {|w|
|
||||
w.write f.read
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def read_multipart( src )
|
||||
bound = @header['content-type'].params['boundary']
|
||||
is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/
|
||||
lastbound = "--#{bound}--"
|
||||
|
||||
ports = [ @config.new_preamble_port(self) ]
|
||||
begin
|
||||
f = ports.last.wopen
|
||||
while line = src.gets
|
||||
if is_sep === line
|
||||
f.close
|
||||
break if line.strip == lastbound
|
||||
ports.push @config.new_part_port(self)
|
||||
f = ports.last.wopen
|
||||
else
|
||||
f << line
|
||||
end
|
||||
end
|
||||
@epilogue = (src.read || '')
|
||||
ensure
|
||||
f.close if f and not f.closed?
|
||||
end
|
||||
|
||||
@body_port = ports.shift
|
||||
@parts = ports.map {|p| self.class.new(p, @config) }
|
||||
end
|
||||
|
||||
end # class Mail
|
||||
|
||||
end # module TMail
|
||||
@@ -1,495 +0,0 @@
|
||||
=begin rdoc
|
||||
|
||||
= Mailbox and Mbox interaction class
|
||||
|
||||
=end
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/port'
|
||||
require 'socket'
|
||||
require 'mutex_m'
|
||||
|
||||
|
||||
unless [].respond_to?(:sort_by)
|
||||
module Enumerable#:nodoc:
|
||||
def sort_by
|
||||
map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class MhMailbox
|
||||
|
||||
PORT_CLASS = MhPort
|
||||
|
||||
def initialize( dir )
|
||||
edir = File.expand_path(dir)
|
||||
raise ArgumentError, "not directory: #{dir}"\
|
||||
unless FileTest.directory? edir
|
||||
@dirname = edir
|
||||
@last_file = nil
|
||||
@last_atime = nil
|
||||
end
|
||||
|
||||
def directory
|
||||
@dirname
|
||||
end
|
||||
|
||||
alias dirname directory
|
||||
|
||||
attr_accessor :last_atime
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{@dirname}>"
|
||||
end
|
||||
|
||||
def close
|
||||
end
|
||||
|
||||
def new_port
|
||||
PORT_CLASS.new(next_file_name())
|
||||
end
|
||||
|
||||
def each_port
|
||||
mail_files().each do |path|
|
||||
yield PORT_CLASS.new(path)
|
||||
end
|
||||
@last_atime = Time.now
|
||||
end
|
||||
|
||||
alias each each_port
|
||||
|
||||
def reverse_each_port
|
||||
mail_files().reverse_each do |path|
|
||||
yield PORT_CLASS.new(path)
|
||||
end
|
||||
@last_atime = Time.now
|
||||
end
|
||||
|
||||
alias reverse_each reverse_each_port
|
||||
|
||||
# old #each_mail returns Port
|
||||
#def each_mail
|
||||
# each_port do |port|
|
||||
# yield Mail.new(port)
|
||||
# end
|
||||
#end
|
||||
|
||||
def each_new_port( mtime = nil, &block )
|
||||
mtime ||= @last_atime
|
||||
return each_port(&block) unless mtime
|
||||
return unless File.mtime(@dirname) >= mtime
|
||||
|
||||
mail_files().each do |path|
|
||||
yield PORT_CLASS.new(path) if File.mtime(path) > mtime
|
||||
end
|
||||
@last_atime = Time.now
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mail_files
|
||||
Dir.entries(@dirname)\
|
||||
.select {|s| /\A\d+\z/ === s }\
|
||||
.map {|s| s.to_i }\
|
||||
.sort\
|
||||
.map {|i| "#{@dirname}/#{i}" }\
|
||||
.select {|path| FileTest.file? path }
|
||||
end
|
||||
|
||||
def next_file_name
|
||||
unless n = @last_file
|
||||
n = 0
|
||||
Dir.entries(@dirname)\
|
||||
.select {|s| /\A\d+\z/ === s }\
|
||||
.map {|s| s.to_i }.sort\
|
||||
.each do |i|
|
||||
next unless FileTest.file? "#{@dirname}/#{i}"
|
||||
n = i
|
||||
end
|
||||
end
|
||||
begin
|
||||
n += 1
|
||||
end while FileTest.exist? "#{@dirname}/#{n}"
|
||||
@last_file = n
|
||||
|
||||
"#{@dirname}/#{n}"
|
||||
end
|
||||
|
||||
end # MhMailbox
|
||||
|
||||
MhLoader = MhMailbox
|
||||
|
||||
|
||||
class UNIXMbox
|
||||
|
||||
class << self
|
||||
alias newobj new
|
||||
end
|
||||
|
||||
# Creates a new mailbox object that you can iterate through to collect the
|
||||
# emails from with "each_port".
|
||||
#
|
||||
# You need to pass it a filename of a unix mailbox format file, the format of this
|
||||
# file can be researched at this page at {wikipedia}[link:http://en.wikipedia.org/wiki/Mbox]
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# +filename+: The filename of the mailbox you want to open
|
||||
#
|
||||
# +tmpdir+: Can be set to override TMail using the system environment's temp dir. TMail will first
|
||||
# use the temp dir specified by you (if any) or then the temp dir specified in the Environment's TEMP
|
||||
# value then the value in the Environment's TMP value or failing all of the above, '/tmp'
|
||||
#
|
||||
# +readonly+: If set to false, each email you take from the mail box will be removed from the mailbox.
|
||||
# default is *false* - ie, it *WILL* truncate your mailbox file to ZERO once it has read the emails out.
|
||||
#
|
||||
# ==== Options:
|
||||
#
|
||||
# None
|
||||
#
|
||||
# ==== Examples:
|
||||
#
|
||||
# # First show using readonly true:
|
||||
#
|
||||
# require 'ftools'
|
||||
# File.size("../test/fixtures/mailbox")
|
||||
# #=> 20426
|
||||
#
|
||||
# mailbox = TMail::UNIXMbox.new("../test/fixtures/mailbox", nil, true)
|
||||
# #=> #<TMail::UNIXMbox:0x14a2aa8 @readonly=true.....>
|
||||
#
|
||||
# mailbox.each_port do |port|
|
||||
# mail = TMail::Mail.new(port)
|
||||
# puts mail.subject
|
||||
# end
|
||||
# #Testing mailbox 1
|
||||
# #Testing mailbox 2
|
||||
# #Testing mailbox 3
|
||||
# #Testing mailbox 4
|
||||
# require 'ftools'
|
||||
# File.size?("../test/fixtures/mailbox")
|
||||
# #=> 20426
|
||||
#
|
||||
# # Now show with readonly set to the default false
|
||||
#
|
||||
# mailbox = TMail::UNIXMbox.new("../test/fixtures/mailbox")
|
||||
# #=> #<TMail::UNIXMbox:0x14a2aa8 @readonly=false.....>
|
||||
#
|
||||
# mailbox.each_port do |port|
|
||||
# mail = TMail::Mail.new(port)
|
||||
# puts mail.subject
|
||||
# end
|
||||
# #Testing mailbox 1
|
||||
# #Testing mailbox 2
|
||||
# #Testing mailbox 3
|
||||
# #Testing mailbox 4
|
||||
#
|
||||
# File.size?("../test/fixtures/mailbox")
|
||||
# #=> nil
|
||||
def UNIXMbox.new( filename, tmpdir = nil, readonly = false )
|
||||
tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp'
|
||||
newobj(filename, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false)
|
||||
end
|
||||
|
||||
def UNIXMbox.lock( fname )
|
||||
begin
|
||||
f = File.open(fname, 'r+')
|
||||
f.flock File::LOCK_EX
|
||||
yield f
|
||||
ensure
|
||||
f.flock File::LOCK_UN
|
||||
f.close if f and not f.closed?
|
||||
end
|
||||
end
|
||||
|
||||
def UNIXMbox.static_new( fname, dir, readonly = false )
|
||||
newobj(fname, dir, readonly, true)
|
||||
end
|
||||
|
||||
def initialize( fname, mhdir, readonly, static )
|
||||
@filename = fname
|
||||
@readonly = readonly
|
||||
@closed = false
|
||||
|
||||
Dir.mkdir mhdir
|
||||
@real = MhMailbox.new(mhdir)
|
||||
@finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static)
|
||||
ObjectSpace.define_finalizer self, @finalizer
|
||||
end
|
||||
|
||||
def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p )
|
||||
lambda {
|
||||
if writeback_p
|
||||
lock(mboxfile) {|f|
|
||||
mh.each_port do |port|
|
||||
f.puts create_from_line(port)
|
||||
port.ropen {|r|
|
||||
f.puts r.read
|
||||
}
|
||||
end
|
||||
}
|
||||
end
|
||||
if cleanup_p
|
||||
Dir.foreach(mh.dirname) do |fname|
|
||||
next if /\A\.\.?\z/ === fname
|
||||
File.unlink "#{mh.dirname}/#{fname}"
|
||||
end
|
||||
Dir.rmdir mh.dirname
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# make _From line
|
||||
def UNIXMbox.create_from_line( port )
|
||||
sprintf 'From %s %s',
|
||||
fromaddr(), TextUtils.time2str(File.mtime(port.filename))
|
||||
end
|
||||
|
||||
def UNIXMbox.fromaddr(port)
|
||||
h = HeaderField.new_from_port(port, 'Return-Path') ||
|
||||
HeaderField.new_from_port(port, 'From') ||
|
||||
HeaderField.new_from_port(port, 'EnvelopeSender') or return 'nobody'
|
||||
a = h.addrs[0] or return 'nobody'
|
||||
a.spec
|
||||
end
|
||||
|
||||
def close
|
||||
return if @closed
|
||||
|
||||
ObjectSpace.undefine_finalizer self
|
||||
@finalizer.call
|
||||
@finalizer = nil
|
||||
@real = nil
|
||||
@closed = true
|
||||
@updated = nil
|
||||
end
|
||||
|
||||
def each_port( &block )
|
||||
close_check
|
||||
update
|
||||
@real.each_port(&block)
|
||||
end
|
||||
|
||||
alias each each_port
|
||||
|
||||
def reverse_each_port( &block )
|
||||
close_check
|
||||
update
|
||||
@real.reverse_each_port(&block)
|
||||
end
|
||||
|
||||
alias reverse_each reverse_each_port
|
||||
|
||||
# old #each_mail returns Port
|
||||
#def each_mail( &block )
|
||||
# each_port do |port|
|
||||
# yield Mail.new(port)
|
||||
# end
|
||||
#end
|
||||
|
||||
def each_new_port( mtime = nil )
|
||||
close_check
|
||||
update
|
||||
@real.each_new_port(mtime) {|p| yield p }
|
||||
end
|
||||
|
||||
def new_port
|
||||
close_check
|
||||
@real.new_port
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def close_check
|
||||
@closed and raise ArgumentError, 'accessing already closed mbox'
|
||||
end
|
||||
|
||||
def update
|
||||
return if FileTest.zero?(@filename)
|
||||
return if @updated and File.mtime(@filename) < @updated
|
||||
w = nil
|
||||
port = nil
|
||||
time = nil
|
||||
UNIXMbox.lock(@filename) {|f|
|
||||
begin
|
||||
f.each do |line|
|
||||
if /\AFrom / === line
|
||||
w.close if w
|
||||
File.utime time, time, port.filename if time
|
||||
|
||||
port = @real.new_port
|
||||
w = port.wopen
|
||||
time = fromline2time(line)
|
||||
else
|
||||
w.print line if w
|
||||
end
|
||||
end
|
||||
ensure
|
||||
if w and not w.closed?
|
||||
w.close
|
||||
File.utime time, time, port.filename if time
|
||||
end
|
||||
end
|
||||
f.truncate(0) unless @readonly
|
||||
@updated = Time.now
|
||||
}
|
||||
end
|
||||
|
||||
def fromline2time( line )
|
||||
m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \
|
||||
or return nil
|
||||
Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i)
|
||||
end
|
||||
|
||||
end # UNIXMbox
|
||||
|
||||
MboxLoader = UNIXMbox
|
||||
|
||||
|
||||
class Maildir
|
||||
|
||||
extend Mutex_m
|
||||
|
||||
PORT_CLASS = MaildirPort
|
||||
|
||||
@seq = 0
|
||||
def Maildir.unique_number
|
||||
synchronize {
|
||||
@seq += 1
|
||||
return @seq
|
||||
}
|
||||
end
|
||||
|
||||
def initialize( dir = nil )
|
||||
@dirname = dir || ENV['MAILDIR']
|
||||
raise ArgumentError, "not directory: #{@dirname}"\
|
||||
unless FileTest.directory? @dirname
|
||||
@new = "#{@dirname}/new"
|
||||
@tmp = "#{@dirname}/tmp"
|
||||
@cur = "#{@dirname}/cur"
|
||||
end
|
||||
|
||||
def directory
|
||||
@dirname
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{@dirname}>"
|
||||
end
|
||||
|
||||
def close
|
||||
end
|
||||
|
||||
def each_port
|
||||
mail_files(@cur).each do |path|
|
||||
yield PORT_CLASS.new(path)
|
||||
end
|
||||
end
|
||||
|
||||
alias each each_port
|
||||
|
||||
def reverse_each_port
|
||||
mail_files(@cur).reverse_each do |path|
|
||||
yield PORT_CLASS.new(path)
|
||||
end
|
||||
end
|
||||
|
||||
alias reverse_each reverse_each_port
|
||||
|
||||
def new_port
|
||||
fname = nil
|
||||
tmpfname = nil
|
||||
newfname = nil
|
||||
|
||||
begin
|
||||
fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
|
||||
|
||||
tmpfname = "#{@tmp}/#{fname}"
|
||||
newfname = "#{@new}/#{fname}"
|
||||
end while FileTest.exist? tmpfname
|
||||
|
||||
if block_given?
|
||||
File.open(tmpfname, 'w') {|f| yield f }
|
||||
File.rename tmpfname, newfname
|
||||
PORT_CLASS.new(newfname)
|
||||
else
|
||||
File.open(tmpfname, 'w') {|f| f.write "\n\n" }
|
||||
PORT_CLASS.new(tmpfname)
|
||||
end
|
||||
end
|
||||
|
||||
def each_new_port
|
||||
mail_files(@new).each do |path|
|
||||
dest = @cur + '/' + File.basename(path)
|
||||
File.rename path, dest
|
||||
yield PORT_CLASS.new(dest)
|
||||
end
|
||||
|
||||
check_tmp
|
||||
end
|
||||
|
||||
TOO_OLD = 60 * 60 * 36 # 36 hour
|
||||
|
||||
def check_tmp
|
||||
old = Time.now.to_i - TOO_OLD
|
||||
|
||||
each_filename(@tmp) do |full, fname|
|
||||
if FileTest.file? full and
|
||||
File.stat(full).mtime.to_i < old
|
||||
File.unlink full
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mail_files( dir )
|
||||
Dir.entries(dir)\
|
||||
.select {|s| s[0] != ?. }\
|
||||
.sort_by {|s| s.slice(/\A\d+/).to_i }\
|
||||
.map {|s| "#{dir}/#{s}" }\
|
||||
.select {|path| FileTest.file? path }
|
||||
end
|
||||
|
||||
def each_filename( dir )
|
||||
Dir.foreach(dir) do |fname|
|
||||
path = "#{dir}/#{fname}"
|
||||
if fname[0] != ?. and FileTest.file? path
|
||||
yield path, fname
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end # Maildir
|
||||
|
||||
MaildirLoader = Maildir
|
||||
|
||||
end # module TMail
|
||||
@@ -1,6 +0,0 @@
|
||||
#:stopdoc:
|
||||
require 'tmail/version'
|
||||
require 'tmail/mail'
|
||||
require 'tmail/mailbox'
|
||||
require 'tmail/core_extensions'
|
||||
#:startdoc:
|
||||
@@ -1,3 +0,0 @@
|
||||
#:stopdoc:
|
||||
require 'tmail/mailbox'
|
||||
#:startdoc:
|
||||
@@ -1,248 +0,0 @@
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
#:stopdoc:
|
||||
require 'nkf'
|
||||
#:startdoc:
|
||||
|
||||
module TMail
|
||||
|
||||
class Mail
|
||||
|
||||
def send_to( smtp )
|
||||
do_send_to(smtp) do
|
||||
ready_to_send
|
||||
end
|
||||
end
|
||||
|
||||
def send_text_to( smtp )
|
||||
do_send_to(smtp) do
|
||||
ready_to_send
|
||||
mime_encode
|
||||
end
|
||||
end
|
||||
|
||||
def do_send_to( smtp )
|
||||
from = from_address or raise ArgumentError, 'no from address'
|
||||
(dests = destinations).empty? and raise ArgumentError, 'no receipient'
|
||||
yield
|
||||
send_to_0 smtp, from, dests
|
||||
end
|
||||
private :do_send_to
|
||||
|
||||
def send_to_0( smtp, from, to )
|
||||
smtp.ready(from, to) do |f|
|
||||
encoded "\r\n", 'j', f, ''
|
||||
end
|
||||
end
|
||||
|
||||
def ready_to_send
|
||||
delete_no_send_fields
|
||||
add_message_id
|
||||
add_date
|
||||
end
|
||||
|
||||
NOSEND_FIELDS = %w(
|
||||
received
|
||||
bcc
|
||||
)
|
||||
|
||||
def delete_no_send_fields
|
||||
NOSEND_FIELDS.each do |nm|
|
||||
delete nm
|
||||
end
|
||||
delete_if {|n,v| v.empty? }
|
||||
end
|
||||
|
||||
def add_message_id( fqdn = nil )
|
||||
self.message_id = ::TMail::new_message_id(fqdn)
|
||||
end
|
||||
|
||||
def add_date
|
||||
self.date = Time.now
|
||||
end
|
||||
|
||||
def mime_encode
|
||||
if parts.empty?
|
||||
mime_encode_singlepart
|
||||
else
|
||||
mime_encode_multipart true
|
||||
end
|
||||
end
|
||||
|
||||
def mime_encode_singlepart
|
||||
self.mime_version = '1.0'
|
||||
b = body
|
||||
if NKF.guess(b) != NKF::BINARY
|
||||
mime_encode_text b
|
||||
else
|
||||
mime_encode_binary b
|
||||
end
|
||||
end
|
||||
|
||||
def mime_encode_text( body )
|
||||
self.body = NKF.nkf('-j -m0', body)
|
||||
self.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
|
||||
self.encoding = '7bit'
|
||||
end
|
||||
|
||||
def mime_encode_binary( body )
|
||||
self.body = [body].pack('m')
|
||||
self.set_content_type 'application', 'octet-stream'
|
||||
self.encoding = 'Base64'
|
||||
end
|
||||
|
||||
def mime_encode_multipart( top = true )
|
||||
self.mime_version = '1.0' if top
|
||||
self.set_content_type 'multipart', 'mixed'
|
||||
e = encoding(nil)
|
||||
if e and not /\A(?:7bit|8bit|binary)\z/i === e
|
||||
raise ArgumentError,
|
||||
'using C.T.Encoding with multipart mail is not permitted'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#:stopdoc:
|
||||
class DeleteFields
|
||||
|
||||
NOSEND_FIELDS = %w(
|
||||
received
|
||||
bcc
|
||||
)
|
||||
|
||||
def initialize( nosend = nil, delempty = true )
|
||||
@no_send_fields = nosend || NOSEND_FIELDS.dup
|
||||
@delete_empty_fields = delempty
|
||||
end
|
||||
|
||||
attr :no_send_fields
|
||||
attr :delete_empty_fields, true
|
||||
|
||||
def exec( mail )
|
||||
@no_send_fields.each do |nm|
|
||||
delete nm
|
||||
end
|
||||
delete_if {|n,v| v.empty? } if @delete_empty_fields
|
||||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
#:stopdoc:
|
||||
class AddMessageId
|
||||
|
||||
def initialize( fqdn = nil )
|
||||
@fqdn = fqdn
|
||||
end
|
||||
|
||||
attr :fqdn, true
|
||||
|
||||
def exec( mail )
|
||||
mail.message_id = ::TMail::new_msgid(@fqdn)
|
||||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
#:stopdoc:
|
||||
class AddDate
|
||||
|
||||
def exec( mail )
|
||||
mail.date = Time.now
|
||||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
#:stopdoc:
|
||||
class MimeEncodeAuto
|
||||
|
||||
def initialize( s = nil, m = nil )
|
||||
@singlepart_composer = s || MimeEncodeSingle.new
|
||||
@multipart_composer = m || MimeEncodeMulti.new
|
||||
end
|
||||
|
||||
attr :singlepart_composer
|
||||
attr :multipart_composer
|
||||
|
||||
def exec( mail )
|
||||
if mail._builtin_multipart?
|
||||
then @multipart_composer
|
||||
else @singlepart_composer end.exec mail
|
||||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
#:stopdoc:
|
||||
class MimeEncodeSingle
|
||||
|
||||
def exec( mail )
|
||||
mail.mime_version = '1.0'
|
||||
b = mail.body
|
||||
if NKF.guess(b) != NKF::BINARY
|
||||
on_text b
|
||||
else
|
||||
on_binary b
|
||||
end
|
||||
end
|
||||
|
||||
def on_text( body )
|
||||
mail.body = NKF.nkf('-j -m0', body)
|
||||
mail.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
|
||||
mail.encoding = '7bit'
|
||||
end
|
||||
|
||||
def on_binary( body )
|
||||
mail.body = [body].pack('m')
|
||||
mail.set_content_type 'application', 'octet-stream'
|
||||
mail.encoding = 'Base64'
|
||||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
#:stopdoc:
|
||||
class MimeEncodeMulti
|
||||
|
||||
def exec( mail, top = true )
|
||||
mail.mime_version = '1.0' if top
|
||||
mail.set_content_type 'multipart', 'mixed'
|
||||
e = encoding(nil)
|
||||
if e and not /\A(?:7bit|8bit|binary)\z/i === e
|
||||
raise ArgumentError,
|
||||
'using C.T.Encoding with multipart mail is not permitted'
|
||||
end
|
||||
mail.parts.each do |m|
|
||||
exec m, false if m._builtin_multipart?
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
end # module TMail
|
||||
@@ -1,132 +0,0 @@
|
||||
=begin rdoc
|
||||
|
||||
= Obsolete methods that are deprecated
|
||||
|
||||
If you really want to see them, go to lib/tmail/obsolete.rb and view to your
|
||||
heart's content.
|
||||
|
||||
=end
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
#:stopdoc:
|
||||
module TMail #:nodoc:
|
||||
|
||||
class Mail
|
||||
alias include? key?
|
||||
alias has_key? key?
|
||||
|
||||
def values
|
||||
ret = []
|
||||
each_field {|v| ret.push v }
|
||||
ret
|
||||
end
|
||||
|
||||
def value?( val )
|
||||
HeaderField === val or return false
|
||||
|
||||
[ @header[val.name.downcase] ].flatten.include? val
|
||||
end
|
||||
|
||||
alias has_value? value?
|
||||
end
|
||||
|
||||
class Mail
|
||||
def from_addr( default = nil )
|
||||
addr, = from_addrs(nil)
|
||||
addr || default
|
||||
end
|
||||
|
||||
def from_address( default = nil )
|
||||
if a = from_addr(nil)
|
||||
a.spec
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
alias from_address= from_addrs=
|
||||
|
||||
def from_phrase( default = nil )
|
||||
if a = from_addr(nil)
|
||||
a.phrase
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
alias msgid message_id
|
||||
alias msgid= message_id=
|
||||
|
||||
alias each_dest each_destination
|
||||
end
|
||||
|
||||
class Address
|
||||
alias route routes
|
||||
alias addr spec
|
||||
|
||||
def spec=( str )
|
||||
@local, @domain = str.split(/@/,2).map {|s| s.split(/\./) }
|
||||
end
|
||||
|
||||
alias addr= spec=
|
||||
alias address= spec=
|
||||
end
|
||||
|
||||
class MhMailbox
|
||||
alias new_mail new_port
|
||||
alias each_mail each_port
|
||||
alias each_newmail each_new_port
|
||||
end
|
||||
class UNIXMbox
|
||||
alias new_mail new_port
|
||||
alias each_mail each_port
|
||||
alias each_newmail each_new_port
|
||||
end
|
||||
class Maildir
|
||||
alias new_mail new_port
|
||||
alias each_mail each_port
|
||||
alias each_newmail each_new_port
|
||||
end
|
||||
|
||||
extend TextUtils
|
||||
|
||||
class << self
|
||||
alias msgid? message_id?
|
||||
alias boundary new_boundary
|
||||
alias msgid new_message_id
|
||||
alias new_msgid new_message_id
|
||||
end
|
||||
|
||||
def Mail.boundary
|
||||
::TMail.new_boundary
|
||||
end
|
||||
|
||||
def Mail.msgid
|
||||
::TMail.new_message_id
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
#:startdoc:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,379 +0,0 @@
|
||||
=begin rdoc
|
||||
|
||||
= Port class
|
||||
|
||||
=end
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/stringio'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class Port
|
||||
def reproducible?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
###
|
||||
### FilePort
|
||||
###
|
||||
|
||||
class FilePort < Port
|
||||
|
||||
def initialize( fname )
|
||||
@filename = File.expand_path(fname)
|
||||
super()
|
||||
end
|
||||
|
||||
attr_reader :filename
|
||||
|
||||
alias ident filename
|
||||
|
||||
def ==( other )
|
||||
other.respond_to?(:filename) and @filename == other.filename
|
||||
end
|
||||
|
||||
alias eql? ==
|
||||
|
||||
def hash
|
||||
@filename.hash
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:#{@filename}>"
|
||||
end
|
||||
|
||||
def reproducible?
|
||||
true
|
||||
end
|
||||
|
||||
def size
|
||||
File.size @filename
|
||||
end
|
||||
|
||||
|
||||
def ropen( &block )
|
||||
File.open(@filename, &block)
|
||||
end
|
||||
|
||||
def wopen( &block )
|
||||
File.open(@filename, 'w', &block)
|
||||
end
|
||||
|
||||
def aopen( &block )
|
||||
File.open(@filename, 'a', &block)
|
||||
end
|
||||
|
||||
|
||||
def read_all
|
||||
ropen {|f|
|
||||
return f.read
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def remove
|
||||
File.unlink @filename
|
||||
end
|
||||
|
||||
def move_to( port )
|
||||
begin
|
||||
File.link @filename, port.filename
|
||||
rescue Errno::EXDEV
|
||||
copy_to port
|
||||
end
|
||||
File.unlink @filename
|
||||
end
|
||||
|
||||
alias mv move_to
|
||||
|
||||
def copy_to( port )
|
||||
if FilePort === port
|
||||
copy_file @filename, port.filename
|
||||
else
|
||||
File.open(@filename) {|r|
|
||||
port.wopen {|w|
|
||||
while s = r.sysread(4096)
|
||||
w.write << s
|
||||
end
|
||||
} }
|
||||
end
|
||||
end
|
||||
|
||||
alias cp copy_to
|
||||
|
||||
private
|
||||
|
||||
# from fileutils.rb
|
||||
def copy_file( src, dest )
|
||||
st = r = w = nil
|
||||
|
||||
File.open(src, 'rb') {|r|
|
||||
File.open(dest, 'wb') {|w|
|
||||
st = r.stat
|
||||
begin
|
||||
while true
|
||||
w.write r.sysread(st.blksize)
|
||||
end
|
||||
rescue EOFError
|
||||
end
|
||||
} }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
module MailFlags
|
||||
|
||||
def seen=( b )
|
||||
set_status 'S', b
|
||||
end
|
||||
|
||||
def seen?
|
||||
get_status 'S'
|
||||
end
|
||||
|
||||
def replied=( b )
|
||||
set_status 'R', b
|
||||
end
|
||||
|
||||
def replied?
|
||||
get_status 'R'
|
||||
end
|
||||
|
||||
def flagged=( b )
|
||||
set_status 'F', b
|
||||
end
|
||||
|
||||
def flagged?
|
||||
get_status 'F'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def procinfostr( str, tag, true_p )
|
||||
a = str.upcase.split(//)
|
||||
a.push true_p ? tag : nil
|
||||
a.delete tag unless true_p
|
||||
a.compact.sort.join('').squeeze
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class MhPort < FilePort
|
||||
|
||||
include MailFlags
|
||||
|
||||
private
|
||||
|
||||
def set_status( tag, flag )
|
||||
begin
|
||||
tmpfile = @filename + '.tmailtmp.' + $$.to_s
|
||||
File.open(tmpfile, 'w') {|f|
|
||||
write_status f, tag, flag
|
||||
}
|
||||
File.unlink @filename
|
||||
File.link tmpfile, @filename
|
||||
ensure
|
||||
File.unlink tmpfile
|
||||
end
|
||||
end
|
||||
|
||||
def write_status( f, tag, flag )
|
||||
stat = ''
|
||||
File.open(@filename) {|r|
|
||||
while line = r.gets
|
||||
if line.strip.empty?
|
||||
break
|
||||
elsif m = /\AX-TMail-Status:/i.match(line)
|
||||
stat = m.post_match.strip
|
||||
else
|
||||
f.print line
|
||||
end
|
||||
end
|
||||
|
||||
s = procinfostr(stat, tag, flag)
|
||||
f.puts 'X-TMail-Status: ' + s unless s.empty?
|
||||
f.puts
|
||||
|
||||
while s = r.read(2048)
|
||||
f.write s
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def get_status( tag )
|
||||
File.foreach(@filename) {|line|
|
||||
return false if line.strip.empty?
|
||||
if m = /\AX-TMail-Status:/i.match(line)
|
||||
return m.post_match.strip.include?(tag[0])
|
||||
end
|
||||
}
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class MaildirPort < FilePort
|
||||
|
||||
def move_to_new
|
||||
new = replace_dir(@filename, 'new')
|
||||
File.rename @filename, new
|
||||
@filename = new
|
||||
end
|
||||
|
||||
def move_to_cur
|
||||
new = replace_dir(@filename, 'cur')
|
||||
File.rename @filename, new
|
||||
@filename = new
|
||||
end
|
||||
|
||||
def replace_dir( path, dir )
|
||||
"#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}"
|
||||
end
|
||||
private :replace_dir
|
||||
|
||||
|
||||
include MailFlags
|
||||
|
||||
private
|
||||
|
||||
MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/
|
||||
|
||||
def set_status( tag, flag )
|
||||
if m = MAIL_FILE.match(File.basename(@filename))
|
||||
s, uniq, type, info, = m.to_a
|
||||
return if type and type != '2' # do not change anything
|
||||
newname = File.dirname(@filename) + '/' +
|
||||
uniq + ':2,' + procinfostr(info.to_s, tag, flag)
|
||||
else
|
||||
newname = @filename + ':2,' + tag
|
||||
end
|
||||
|
||||
File.link @filename, newname
|
||||
File.unlink @filename
|
||||
@filename = newname
|
||||
end
|
||||
|
||||
def get_status( tag )
|
||||
m = MAIL_FILE.match(File.basename(@filename)) or return false
|
||||
m[2] == '2' and m[3].to_s.include?(tag[0])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
###
|
||||
### StringPort
|
||||
###
|
||||
|
||||
class StringPort < Port
|
||||
|
||||
def initialize( str = '' )
|
||||
@buffer = str
|
||||
super()
|
||||
end
|
||||
|
||||
def string
|
||||
@buffer
|
||||
end
|
||||
|
||||
def to_s
|
||||
@buffer.dup
|
||||
end
|
||||
|
||||
alias read_all to_s
|
||||
|
||||
def size
|
||||
@buffer.size
|
||||
end
|
||||
|
||||
def ==( other )
|
||||
StringPort === other and @buffer.equal? other.string
|
||||
end
|
||||
|
||||
alias eql? ==
|
||||
|
||||
def hash
|
||||
@buffer.object_id.hash
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>"
|
||||
end
|
||||
|
||||
def reproducible?
|
||||
true
|
||||
end
|
||||
|
||||
def ropen( &block )
|
||||
@buffer or raise Errno::ENOENT, "#{inspect} is already removed"
|
||||
StringInput.open(@buffer, &block)
|
||||
end
|
||||
|
||||
def wopen( &block )
|
||||
@buffer = ''
|
||||
StringOutput.new(@buffer, &block)
|
||||
end
|
||||
|
||||
def aopen( &block )
|
||||
@buffer ||= ''
|
||||
StringOutput.new(@buffer, &block)
|
||||
end
|
||||
|
||||
def remove
|
||||
@buffer = nil
|
||||
end
|
||||
|
||||
alias rm remove
|
||||
|
||||
def copy_to( port )
|
||||
port.wopen {|f|
|
||||
f.write @buffer
|
||||
}
|
||||
end
|
||||
|
||||
alias cp copy_to
|
||||
|
||||
def move_to( port )
|
||||
if StringPort === port
|
||||
str = @buffer
|
||||
port.instance_eval { @buffer = str }
|
||||
else
|
||||
copy_to port
|
||||
end
|
||||
remove
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
@@ -1,118 +0,0 @@
|
||||
=begin rdoc
|
||||
|
||||
= Quoting methods
|
||||
|
||||
=end
|
||||
module TMail
|
||||
class Mail
|
||||
def subject(to_charset = 'utf-8')
|
||||
Unquoter.unquote_and_convert_to(quoted_subject, to_charset)
|
||||
end
|
||||
|
||||
def unquoted_body(to_charset = 'utf-8')
|
||||
from_charset = sub_header("content-type", "charset")
|
||||
case (content_transfer_encoding || "7bit").downcase
|
||||
when "quoted-printable"
|
||||
# the default charset is set to iso-8859-1 instead of 'us-ascii'.
|
||||
# This is needed as many mailer do not set the charset but send in ISO. This is only used if no charset is set.
|
||||
if !from_charset.blank? && from_charset.downcase == 'us-ascii'
|
||||
from_charset = 'iso-8859-1'
|
||||
end
|
||||
|
||||
Unquoter.unquote_quoted_printable_and_convert_to(quoted_body,
|
||||
to_charset, from_charset, true)
|
||||
when "base64"
|
||||
Unquoter.unquote_base64_and_convert_to(quoted_body, to_charset,
|
||||
from_charset)
|
||||
when "7bit", "8bit"
|
||||
Unquoter.convert_to(quoted_body, to_charset, from_charset)
|
||||
when "binary"
|
||||
quoted_body
|
||||
else
|
||||
quoted_body
|
||||
end
|
||||
end
|
||||
|
||||
def body(to_charset = 'utf-8', &block)
|
||||
attachment_presenter = block || Proc.new { |file_name| "Attachment: #{file_name}\n" }
|
||||
|
||||
if multipart?
|
||||
parts.collect { |part|
|
||||
header = part["content-type"]
|
||||
|
||||
if part.multipart?
|
||||
part.body(to_charset, &attachment_presenter)
|
||||
elsif header.nil?
|
||||
""
|
||||
elsif !attachment?(part)
|
||||
part.unquoted_body(to_charset)
|
||||
else
|
||||
attachment_presenter.call(header["name"] || "(unnamed)")
|
||||
end
|
||||
}.join
|
||||
else
|
||||
unquoted_body(to_charset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Unquoter
|
||||
class << self
|
||||
def unquote_and_convert_to(text, to_charset, from_charset = "iso-8859-1", preserve_underscores=false)
|
||||
return "" if text.nil?
|
||||
text.gsub(/(.*?)(?:(?:=\?(.*?)\?(.)\?(.*?)\?=)|$)/) do
|
||||
before = $1
|
||||
from_charset = $2
|
||||
quoting_method = $3
|
||||
text = $4
|
||||
|
||||
before = convert_to(before, to_charset, from_charset) if before.length > 0
|
||||
before + case quoting_method
|
||||
when "q", "Q" then
|
||||
unquote_quoted_printable_and_convert_to(text, to_charset, from_charset, preserve_underscores)
|
||||
when "b", "B" then
|
||||
unquote_base64_and_convert_to(text, to_charset, from_charset)
|
||||
when nil then
|
||||
# will be nil at the end of the string, due to the nature of
|
||||
# the regex used.
|
||||
""
|
||||
else
|
||||
raise "unknown quoting method #{quoting_method.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false)
|
||||
text = text.gsub(/_/, " ") unless preserve_underscores
|
||||
text = text.gsub(/\r\n|\r/, "\n") # normalize newlines
|
||||
convert_to(text.unpack("M*").first, to, from)
|
||||
end
|
||||
|
||||
def unquote_base64_and_convert_to(text, to, from)
|
||||
convert_to(Base64.decode(text), to, from)
|
||||
end
|
||||
|
||||
begin
|
||||
require 'iconv'
|
||||
def convert_to(text, to, from)
|
||||
return text unless to && from
|
||||
text ? Iconv.iconv(to, from, text).first : ""
|
||||
rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
|
||||
# the 'from' parameter specifies a charset other than what the text
|
||||
# actually is...not much we can do in this case but just return the
|
||||
# unconverted text.
|
||||
#
|
||||
# Ditto if either parameter represents an unknown charset, like
|
||||
# X-UNKNOWN.
|
||||
text
|
||||
end
|
||||
rescue LoadError
|
||||
# Not providing quoting support
|
||||
def convert_to(text, to, from)
|
||||
warn "Action Mailer: iconv not loaded; ignoring conversion from #{from} to #{to} (#{__FILE__}:#{__LINE__})"
|
||||
text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,58 +0,0 @@
|
||||
#:stopdoc:
|
||||
require 'rbconfig'
|
||||
|
||||
# Attempts to require anative extension.
|
||||
# Falls back to pure-ruby version, if it fails.
|
||||
#
|
||||
# This uses Config::CONFIG['arch'] from rbconfig.
|
||||
|
||||
def require_arch(fname)
|
||||
arch = Config::CONFIG['arch']
|
||||
begin
|
||||
path = File.join("tmail", arch, fname)
|
||||
require path
|
||||
rescue LoadError => e
|
||||
# try pre-built Windows binaries
|
||||
if arch =~ /mswin/
|
||||
require File.join("tmail", 'mswin32', fname)
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# def require_arch(fname)
|
||||
# dext = Config::CONFIG['DLEXT']
|
||||
# begin
|
||||
# if File.extname(fname) == dext
|
||||
# path = fname
|
||||
# else
|
||||
# path = File.join("tmail","#{fname}.#{dext}")
|
||||
# end
|
||||
# require path
|
||||
# rescue LoadError => e
|
||||
# begin
|
||||
# arch = Config::CONFIG['arch']
|
||||
# path = File.join("tmail", arch, "#{fname}.#{dext}")
|
||||
# require path
|
||||
# rescue LoadError
|
||||
# case path
|
||||
# when /i686/
|
||||
# path.sub!('i686', 'i586')
|
||||
# when /i586/
|
||||
# path.sub!('i586', 'i486')
|
||||
# when /i486/
|
||||
# path.sub!('i486', 'i386')
|
||||
# else
|
||||
# begin
|
||||
# require fname + '.rb'
|
||||
# rescue LoadError
|
||||
# raise e
|
||||
# end
|
||||
# end
|
||||
# retry
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#:startdoc:
|
||||
@@ -1,49 +0,0 @@
|
||||
=begin rdoc
|
||||
|
||||
= Scanner for TMail
|
||||
|
||||
=end
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
#:stopdoc:
|
||||
#require 'tmail/require_arch'
|
||||
require 'tmail/utils'
|
||||
require 'tmail/config'
|
||||
|
||||
module TMail
|
||||
# NOTE: It woiuld be nice if these two libs could boith be called "tmailscanner", and
|
||||
# the native extension would have precedence. However RubyGems boffs that up b/c
|
||||
# it does not gaurantee load_path order.
|
||||
begin
|
||||
raise LoadError, 'Turned off native extentions by user choice' if ENV['NORUBYEXT']
|
||||
require('tmail/tmailscanner') # c extension
|
||||
Scanner = TMailScanner
|
||||
rescue LoadError
|
||||
require 'tmail/scanner_r'
|
||||
Scanner = TMailScanner
|
||||
end
|
||||
end
|
||||
#:stopdoc:
|
||||
@@ -1,261 +0,0 @@
|
||||
# scanner_r.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
#:stopdoc:
|
||||
require 'tmail/config'
|
||||
|
||||
module TMail
|
||||
|
||||
class TMailScanner
|
||||
|
||||
Version = '1.2.3'
|
||||
Version.freeze
|
||||
|
||||
MIME_HEADERS = {
|
||||
:CTYPE => true,
|
||||
:CENCODING => true,
|
||||
:CDISPOSITION => true
|
||||
}
|
||||
|
||||
alnum = 'a-zA-Z0-9'
|
||||
atomsyms = %q[ _#!$%&`'*+-{|}~^/=? ].strip
|
||||
tokensyms = %q[ _#!$%&`'*+-{|}~^@. ].strip
|
||||
atomchars = alnum + Regexp.quote(atomsyms)
|
||||
tokenchars = alnum + Regexp.quote(tokensyms)
|
||||
iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B'
|
||||
|
||||
eucstr = "(?:[\xa1-\xfe][\xa1-\xfe])+"
|
||||
sjisstr = "(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+"
|
||||
utf8str = "(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+"
|
||||
|
||||
quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n
|
||||
domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n
|
||||
comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n
|
||||
|
||||
quoted_without_iso2022 = /\A[^\\"]+/n
|
||||
domlit_without_iso2022 = /\A[^\\\]]+/n
|
||||
comment_without_iso2022 = /\A[^\\()]+/n
|
||||
|
||||
PATTERN_TABLE = {}
|
||||
PATTERN_TABLE['EUC'] =
|
||||
[
|
||||
/\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n,
|
||||
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n,
|
||||
quoted_with_iso2022,
|
||||
domlit_with_iso2022,
|
||||
comment_with_iso2022
|
||||
]
|
||||
PATTERN_TABLE['SJIS'] =
|
||||
[
|
||||
/\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n,
|
||||
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n,
|
||||
quoted_with_iso2022,
|
||||
domlit_with_iso2022,
|
||||
comment_with_iso2022
|
||||
]
|
||||
PATTERN_TABLE['UTF8'] =
|
||||
[
|
||||
/\A(?:[#{atomchars}]+|#{utf8str})+/n,
|
||||
/\A(?:[#{tokenchars}]+|#{utf8str})+/n,
|
||||
quoted_without_iso2022,
|
||||
domlit_without_iso2022,
|
||||
comment_without_iso2022
|
||||
]
|
||||
PATTERN_TABLE['NONE'] =
|
||||
[
|
||||
/\A[#{atomchars}]+/n,
|
||||
/\A[#{tokenchars}]+/n,
|
||||
quoted_without_iso2022,
|
||||
domlit_without_iso2022,
|
||||
comment_without_iso2022
|
||||
]
|
||||
|
||||
|
||||
def initialize( str, scantype, comments )
|
||||
init_scanner str
|
||||
@comments = comments || []
|
||||
@debug = false
|
||||
|
||||
# fix scanner mode
|
||||
@received = (scantype == :RECEIVED)
|
||||
@is_mime_header = MIME_HEADERS[scantype]
|
||||
|
||||
atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[TMail.KCODE]
|
||||
@word_re = (MIME_HEADERS[scantype] ? token : atom)
|
||||
end
|
||||
|
||||
attr_accessor :debug
|
||||
|
||||
def scan( &block )
|
||||
if @debug
|
||||
scan_main do |arr|
|
||||
s, v = arr
|
||||
printf "%7d %-10s %s\n",
|
||||
rest_size(),
|
||||
s.respond_to?(:id2name) ? s.id2name : s.inspect,
|
||||
v.inspect
|
||||
yield arr
|
||||
end
|
||||
else
|
||||
scan_main(&block)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
RECV_TOKEN = {
|
||||
'from' => :FROM,
|
||||
'by' => :BY,
|
||||
'via' => :VIA,
|
||||
'with' => :WITH,
|
||||
'id' => :ID,
|
||||
'for' => :FOR
|
||||
}
|
||||
|
||||
def scan_main
|
||||
until eof?
|
||||
if skip(/\A[\n\r\t ]+/n) # LWSP
|
||||
break if eof?
|
||||
end
|
||||
|
||||
if s = readstr(@word_re)
|
||||
if @is_mime_header
|
||||
yield [:TOKEN, s]
|
||||
else
|
||||
# atom
|
||||
if /\A\d+\z/ === s
|
||||
yield [:DIGIT, s]
|
||||
elsif @received
|
||||
yield [RECV_TOKEN[s.downcase] || :ATOM, s]
|
||||
else
|
||||
yield [:ATOM, s]
|
||||
end
|
||||
end
|
||||
|
||||
elsif skip(/\A"/)
|
||||
yield [:QUOTED, scan_quoted_word()]
|
||||
|
||||
elsif skip(/\A\[/)
|
||||
yield [:DOMLIT, scan_domain_literal()]
|
||||
|
||||
elsif skip(/\A\(/)
|
||||
@comments.push scan_comment()
|
||||
|
||||
else
|
||||
c = readchar()
|
||||
yield [c, c]
|
||||
end
|
||||
end
|
||||
|
||||
yield [false, '$']
|
||||
end
|
||||
|
||||
def scan_quoted_word
|
||||
scan_qstr(@quoted_re, /\A"/, 'quoted-word')
|
||||
end
|
||||
|
||||
def scan_domain_literal
|
||||
'[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']'
|
||||
end
|
||||
|
||||
def scan_qstr( pattern, terminal, type )
|
||||
result = ''
|
||||
until eof?
|
||||
if s = readstr(pattern) then result << s
|
||||
elsif skip(terminal) then return result
|
||||
elsif skip(/\A\\/) then result << readchar()
|
||||
else
|
||||
raise "TMail FATAL: not match in #{type}"
|
||||
end
|
||||
end
|
||||
scan_error! "found unterminated #{type}"
|
||||
end
|
||||
|
||||
def scan_comment
|
||||
result = ''
|
||||
nest = 1
|
||||
content = @comment_re
|
||||
|
||||
until eof?
|
||||
if s = readstr(content) then result << s
|
||||
elsif skip(/\A\)/) then nest -= 1
|
||||
return result if nest == 0
|
||||
result << ')'
|
||||
elsif skip(/\A\(/) then nest += 1
|
||||
result << '('
|
||||
elsif skip(/\A\\/) then result << readchar()
|
||||
else
|
||||
raise 'TMail FATAL: not match in comment'
|
||||
end
|
||||
end
|
||||
scan_error! 'found unterminated comment'
|
||||
end
|
||||
|
||||
# string scanner
|
||||
|
||||
def init_scanner( str )
|
||||
@src = str
|
||||
end
|
||||
|
||||
def eof?
|
||||
@src.empty?
|
||||
end
|
||||
|
||||
def rest_size
|
||||
@src.size
|
||||
end
|
||||
|
||||
def readstr( re )
|
||||
if m = re.match(@src)
|
||||
@src = m.post_match
|
||||
m[0]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def readchar
|
||||
readstr(/\A./)
|
||||
end
|
||||
|
||||
def skip( re )
|
||||
if m = re.match(@src)
|
||||
@src = m.post_match
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def scan_error!( msg )
|
||||
raise SyntaxError, msg
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
#:startdoc:
|
||||
@@ -1,280 +0,0 @@
|
||||
# encoding: utf-8
|
||||
=begin rdoc
|
||||
|
||||
= String handling class
|
||||
|
||||
=end
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
class StringInput#:nodoc:
|
||||
|
||||
include Enumerable
|
||||
|
||||
class << self
|
||||
|
||||
def new( str )
|
||||
if block_given?
|
||||
begin
|
||||
f = super
|
||||
yield f
|
||||
ensure
|
||||
f.close if f
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
alias open new
|
||||
|
||||
end
|
||||
|
||||
def initialize( str )
|
||||
@src = str
|
||||
@pos = 0
|
||||
@closed = false
|
||||
@lineno = 0
|
||||
end
|
||||
|
||||
attr_reader :lineno
|
||||
|
||||
def string
|
||||
@src
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>"
|
||||
end
|
||||
|
||||
def close
|
||||
stream_check!
|
||||
@pos = nil
|
||||
@closed = true
|
||||
end
|
||||
|
||||
def closed?
|
||||
@closed
|
||||
end
|
||||
|
||||
def pos
|
||||
stream_check!
|
||||
[@pos, @src.size].min
|
||||
end
|
||||
|
||||
alias tell pos
|
||||
|
||||
def seek( offset, whence = IO::SEEK_SET )
|
||||
stream_check!
|
||||
case whence
|
||||
when IO::SEEK_SET
|
||||
@pos = offset
|
||||
when IO::SEEK_CUR
|
||||
@pos += offset
|
||||
when IO::SEEK_END
|
||||
@pos = @src.size - offset
|
||||
else
|
||||
raise ArgumentError, "unknown seek flag: #{whence}"
|
||||
end
|
||||
@pos = 0 if @pos < 0
|
||||
@pos = [@pos, @src.size + 1].min
|
||||
offset
|
||||
end
|
||||
|
||||
def rewind
|
||||
stream_check!
|
||||
@pos = 0
|
||||
end
|
||||
|
||||
def eof?
|
||||
stream_check!
|
||||
@pos > @src.size
|
||||
end
|
||||
|
||||
def each( &block )
|
||||
stream_check!
|
||||
begin
|
||||
@src.each(&block)
|
||||
ensure
|
||||
@pos = 0
|
||||
end
|
||||
end
|
||||
|
||||
def gets
|
||||
stream_check!
|
||||
if idx = @src.index(?\n, @pos)
|
||||
idx += 1 # "\n".size
|
||||
line = @src[ @pos ... idx ]
|
||||
@pos = idx
|
||||
@pos += 1 if @pos == @src.size
|
||||
else
|
||||
line = @src[ @pos .. -1 ]
|
||||
@pos = @src.size + 1
|
||||
end
|
||||
@lineno += 1
|
||||
|
||||
line
|
||||
end
|
||||
|
||||
def getc
|
||||
stream_check!
|
||||
ch = @src[@pos]
|
||||
@pos += 1
|
||||
@pos += 1 if @pos == @src.size
|
||||
ch
|
||||
end
|
||||
|
||||
def read( len = nil )
|
||||
stream_check!
|
||||
return read_all unless len
|
||||
str = @src[@pos, len]
|
||||
@pos += len
|
||||
@pos += 1 if @pos == @src.size
|
||||
str
|
||||
end
|
||||
|
||||
alias sysread read
|
||||
|
||||
def read_all
|
||||
stream_check!
|
||||
return nil if eof?
|
||||
rest = @src[@pos ... @src.size]
|
||||
@pos = @src.size + 1
|
||||
rest
|
||||
end
|
||||
|
||||
def stream_check!
|
||||
@closed and raise IOError, 'closed stream'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class StringOutput#:nodoc:
|
||||
|
||||
class << self
|
||||
|
||||
def new( str = '' )
|
||||
if block_given?
|
||||
begin
|
||||
f = super
|
||||
yield f
|
||||
ensure
|
||||
f.close if f
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
alias open new
|
||||
|
||||
end
|
||||
|
||||
def initialize( str = '' )
|
||||
@dest = str
|
||||
@closed = false
|
||||
end
|
||||
|
||||
def close
|
||||
@closed = true
|
||||
end
|
||||
|
||||
def closed?
|
||||
@closed
|
||||
end
|
||||
|
||||
def string
|
||||
@dest
|
||||
end
|
||||
|
||||
alias value string
|
||||
alias to_str string
|
||||
|
||||
def size
|
||||
@dest.size
|
||||
end
|
||||
|
||||
alias pos size
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:#{@dest ? 'open' : 'closed'},#{object_id}>"
|
||||
end
|
||||
|
||||
def print( *args )
|
||||
stream_check!
|
||||
raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty?
|
||||
args.each do |s|
|
||||
raise ArgumentError, 'nil not allowed' if s.nil?
|
||||
@dest << s.to_s
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def puts( *args )
|
||||
stream_check!
|
||||
args.each do |str|
|
||||
@dest << (s = str.to_s)
|
||||
@dest << "\n" unless s[-1] == ?\n
|
||||
end
|
||||
@dest << "\n" if args.empty?
|
||||
nil
|
||||
end
|
||||
|
||||
def putc( ch )
|
||||
stream_check!
|
||||
@dest << ch.chr
|
||||
nil
|
||||
end
|
||||
|
||||
def printf( *args )
|
||||
stream_check!
|
||||
@dest << sprintf(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
def write( str )
|
||||
stream_check!
|
||||
s = str.to_s
|
||||
@dest << s
|
||||
s.size
|
||||
end
|
||||
|
||||
alias syswrite write
|
||||
|
||||
def <<( str )
|
||||
stream_check!
|
||||
@dest << str.to_s
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stream_check!
|
||||
@closed and raise IOError, 'closed stream'
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,337 +0,0 @@
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
# = TMail - The EMail Swiss Army Knife for Ruby
|
||||
#
|
||||
# The TMail library provides you with a very complete way to handle and manipulate EMails
|
||||
# from within your Ruby programs.
|
||||
#
|
||||
# Used as the backbone for email handling by the Ruby on Rails and Nitro web frameworks as
|
||||
# well as a bunch of other Ruby apps including the Ruby-Talk mailing list to newsgroup email
|
||||
# gateway, it is a proven and reliable email handler that won't let you down.
|
||||
#
|
||||
# Originally created by Minero Aoki, TMail has been recently picked up by Mikel Lindsaar and
|
||||
# is being actively maintained. Numerous backlogged bug fixes have been applied as well as
|
||||
# Ruby 1.9 compatibility and a swath of documentation to boot.
|
||||
#
|
||||
# TMail allows you to treat an email totally as an object and allow you to get on with your
|
||||
# own programming without having to worry about crafting the perfect email address validation
|
||||
# parser, or assembling an email from all it's component parts.
|
||||
#
|
||||
# TMail handles the most complex part of the email - the header. It generates and parses
|
||||
# headers and provides you with instant access to their innards through simple and logically
|
||||
# named accessor and setter methods.
|
||||
#
|
||||
# TMail also provides a wrapper to Net/SMTP as well as Unix Mailbox handling methods to
|
||||
# directly read emails from your unix mailbox, parse them and use them.
|
||||
#
|
||||
# Following is the comprehensive list of methods to access TMail::Mail objects. You can also
|
||||
# check out TMail::Mail, TMail::Address and TMail::Headers for other lists.
|
||||
module TMail
|
||||
|
||||
# Provides an exception to throw on errors in Syntax within TMail's parsers
|
||||
class SyntaxError < StandardError; end
|
||||
|
||||
# Provides a new email boundary to separate parts of the email. This is a random
|
||||
# string based off the current time, so should be fairly unique.
|
||||
#
|
||||
# For Example:
|
||||
#
|
||||
# TMail.new_boundary
|
||||
# #=> "mimepart_47bf656968207_25a8fbb80114"
|
||||
# TMail.new_boundary
|
||||
# #=> "mimepart_47bf66051de4_25a8fbb80240"
|
||||
def TMail.new_boundary
|
||||
'mimepart_' + random_tag
|
||||
end
|
||||
|
||||
# Provides a new email message ID. You can use this to generate unique email message
|
||||
# id's for your email so you can track them.
|
||||
#
|
||||
# Optionally takes a fully qualified domain name (default to the current hostname
|
||||
# returned by Socket.gethostname) that will be appended to the message ID.
|
||||
#
|
||||
# For Example:
|
||||
#
|
||||
# email.message_id = TMail.new_message_id
|
||||
# #=> "<47bf66845380e_25a8fbb80332@baci.local.tmail>"
|
||||
# email.to_s
|
||||
# #=> "Message-Id: <47bf668b633f1_25a8fbb80475@baci.local.tmail>\n\n"
|
||||
# email.message_id = TMail.new_message_id("lindsaar.net")
|
||||
# #=> "<47bf668b633f1_25a8fbb80475@lindsaar.net.tmail>"
|
||||
# email.to_s
|
||||
# #=> "Message-Id: <47bf668b633f1_25a8fbb80475@lindsaar.net.tmail>\n\n"
|
||||
def TMail.new_message_id( fqdn = nil )
|
||||
fqdn ||= ::Socket.gethostname
|
||||
"<#{random_tag()}@#{fqdn}.tmail>"
|
||||
end
|
||||
|
||||
#:stopdoc:
|
||||
def TMail.random_tag #:nodoc:
|
||||
@uniq += 1
|
||||
t = Time.now
|
||||
sprintf('%x%x_%x%x%d%x',
|
||||
t.to_i, t.tv_usec,
|
||||
$$, Thread.current.object_id, @uniq, rand(255))
|
||||
end
|
||||
private_class_method :random_tag
|
||||
|
||||
@uniq = 0
|
||||
|
||||
#:startdoc:
|
||||
|
||||
# Text Utils provides a namespace to define TOKENs, ATOMs, PHRASEs and CONTROL characters that
|
||||
# are OK per RFC 2822.
|
||||
#
|
||||
# It also provides methods you can call to determine if a string is safe
|
||||
module TextUtils
|
||||
|
||||
aspecial = %Q|()<>[]:;.\\,"|
|
||||
tspecial = %Q|()<>[];:\\,"/?=|
|
||||
lwsp = %Q| \t\r\n|
|
||||
control = %Q|\x00-\x1f\x7f-\xff|
|
||||
|
||||
CONTROL_CHAR = /[#{control}]/n
|
||||
ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n
|
||||
PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
|
||||
TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n
|
||||
|
||||
# Returns true if the string supplied is free from characters not allowed as an ATOM
|
||||
def atom_safe?( str )
|
||||
not ATOM_UNSAFE === str
|
||||
end
|
||||
|
||||
# If the string supplied has ATOM unsafe characters in it, will return the string quoted
|
||||
# in double quotes, otherwise returns the string unmodified
|
||||
def quote_atom( str )
|
||||
(ATOM_UNSAFE === str) ? dquote(str) : str
|
||||
end
|
||||
|
||||
# If the string supplied has PHRASE unsafe characters in it, will return the string quoted
|
||||
# in double quotes, otherwise returns the string unmodified
|
||||
def quote_phrase( str )
|
||||
(PHRASE_UNSAFE === str) ? dquote(str) : str
|
||||
end
|
||||
|
||||
# Returns true if the string supplied is free from characters not allowed as a TOKEN
|
||||
def token_safe?( str )
|
||||
not TOKEN_UNSAFE === str
|
||||
end
|
||||
|
||||
# If the string supplied has TOKEN unsafe characters in it, will return the string quoted
|
||||
# in double quotes, otherwise returns the string unmodified
|
||||
def quote_token( str )
|
||||
(TOKEN_UNSAFE === str) ? dquote(str) : str
|
||||
end
|
||||
|
||||
# Wraps supplied string in double quotes unless it is already wrapped
|
||||
# Returns double quoted string
|
||||
def dquote( str ) #:nodoc:
|
||||
unless str =~ /^".*?"$/
|
||||
'"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"'
|
||||
else
|
||||
str
|
||||
end
|
||||
end
|
||||
private :dquote
|
||||
|
||||
# Unwraps supplied string from inside double quotes
|
||||
# Returns unquoted string
|
||||
def unquote( str )
|
||||
str =~ /^"(.*?)"$/ ? $1 : str
|
||||
end
|
||||
|
||||
# Provides a method to join a domain name by it's parts and also makes it
|
||||
# ATOM safe by quoting it as needed
|
||||
def join_domain( arr )
|
||||
arr.map {|i|
|
||||
if /\A\[.*\]\z/ === i
|
||||
i
|
||||
else
|
||||
quote_atom(i)
|
||||
end
|
||||
}.join('.')
|
||||
end
|
||||
|
||||
#:stopdoc:
|
||||
ZONESTR_TABLE = {
|
||||
'jst' => 9 * 60,
|
||||
'eet' => 2 * 60,
|
||||
'bst' => 1 * 60,
|
||||
'met' => 1 * 60,
|
||||
'gmt' => 0,
|
||||
'utc' => 0,
|
||||
'ut' => 0,
|
||||
'nst' => -(3 * 60 + 30),
|
||||
'ast' => -4 * 60,
|
||||
'edt' => -4 * 60,
|
||||
'est' => -5 * 60,
|
||||
'cdt' => -5 * 60,
|
||||
'cst' => -6 * 60,
|
||||
'mdt' => -6 * 60,
|
||||
'mst' => -7 * 60,
|
||||
'pdt' => -7 * 60,
|
||||
'pst' => -8 * 60,
|
||||
'a' => -1 * 60,
|
||||
'b' => -2 * 60,
|
||||
'c' => -3 * 60,
|
||||
'd' => -4 * 60,
|
||||
'e' => -5 * 60,
|
||||
'f' => -6 * 60,
|
||||
'g' => -7 * 60,
|
||||
'h' => -8 * 60,
|
||||
'i' => -9 * 60,
|
||||
# j not use
|
||||
'k' => -10 * 60,
|
||||
'l' => -11 * 60,
|
||||
'm' => -12 * 60,
|
||||
'n' => 1 * 60,
|
||||
'o' => 2 * 60,
|
||||
'p' => 3 * 60,
|
||||
'q' => 4 * 60,
|
||||
'r' => 5 * 60,
|
||||
's' => 6 * 60,
|
||||
't' => 7 * 60,
|
||||
'u' => 8 * 60,
|
||||
'v' => 9 * 60,
|
||||
'w' => 10 * 60,
|
||||
'x' => 11 * 60,
|
||||
'y' => 12 * 60,
|
||||
'z' => 0 * 60
|
||||
}
|
||||
#:startdoc:
|
||||
|
||||
# Takes a time zone string from an EMail and converts it to Unix Time (seconds)
|
||||
def timezone_string_to_unixtime( str )
|
||||
if m = /([\+\-])(\d\d?)(\d\d)/.match(str)
|
||||
sec = (m[2].to_i * 60 + m[3].to_i) * 60
|
||||
m[1] == '-' ? -sec : sec
|
||||
else
|
||||
min = ZONESTR_TABLE[str.downcase] or
|
||||
raise SyntaxError, "wrong timezone format '#{str}'"
|
||||
min * 60
|
||||
end
|
||||
end
|
||||
|
||||
#:stopdoc:
|
||||
WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG )
|
||||
MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun
|
||||
Jul Aug Sep Oct Nov Dec TMailBUG )
|
||||
|
||||
def time2str( tm )
|
||||
# [ruby-list:7928]
|
||||
gmt = Time.at(tm.to_i)
|
||||
gmt.gmtime
|
||||
offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i
|
||||
|
||||
# DO NOT USE strftime: setlocale() breaks it
|
||||
sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d',
|
||||
WDAY[tm.wday], tm.mday, MONTH[tm.month],
|
||||
tm.year, tm.hour, tm.min, tm.sec,
|
||||
*(offset / 60).divmod(60)
|
||||
end
|
||||
|
||||
|
||||
MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/
|
||||
|
||||
def message_id?( str )
|
||||
MESSAGE_ID === str
|
||||
end
|
||||
|
||||
|
||||
MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i
|
||||
|
||||
def mime_encoded?( str )
|
||||
MIME_ENCODED === str
|
||||
end
|
||||
|
||||
|
||||
def decode_params( hash )
|
||||
new = Hash.new
|
||||
encoded = nil
|
||||
hash.each do |key, value|
|
||||
if m = /\*(?:(\d+)\*)?\z/.match(key)
|
||||
((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value
|
||||
else
|
||||
new[key] = to_kcode(value)
|
||||
end
|
||||
end
|
||||
if encoded
|
||||
encoded.each do |key, strings|
|
||||
new[key] = decode_RFC2231(strings.join(''))
|
||||
end
|
||||
end
|
||||
|
||||
new
|
||||
end
|
||||
|
||||
NKF_FLAGS = {
|
||||
'EUC' => '-e -m',
|
||||
'SJIS' => '-s -m'
|
||||
}
|
||||
|
||||
def to_kcode( str )
|
||||
flag = NKF_FLAGS[TMail.KCODE] or return str
|
||||
NKF.nkf(flag, str)
|
||||
end
|
||||
|
||||
RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in
|
||||
|
||||
def decode_RFC2231( str )
|
||||
m = RFC2231_ENCODED.match(str) or return str
|
||||
begin
|
||||
to_kcode(m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr })
|
||||
rescue
|
||||
m.post_match.gsub(/%[\da-f]{2}/in, "")
|
||||
end
|
||||
end
|
||||
|
||||
def quote_boundary
|
||||
# Make sure the Content-Type boundary= parameter is quoted if it contains illegal characters
|
||||
# (to ensure any special characters in the boundary text are escaped from the parser
|
||||
# (such as = in MS Outlook's boundary text))
|
||||
if @body =~ /^(.*)boundary=(.*)$/m
|
||||
preamble = $1
|
||||
remainder = $2
|
||||
if remainder =~ /;/
|
||||
remainder =~ /^(.*?)(;.*)$/m
|
||||
boundary_text = $1
|
||||
post = $2.chomp
|
||||
else
|
||||
boundary_text = remainder.chomp
|
||||
end
|
||||
if boundary_text =~ /[\/\?\=]/
|
||||
boundary_text = "\"#{boundary_text}\"" unless boundary_text =~ /^".*?"$/
|
||||
@body = "#{preamble}boundary=#{boundary_text}#{post}"
|
||||
end
|
||||
end
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,39 +0,0 @@
|
||||
#
|
||||
# version.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
#:stopdoc:
|
||||
module TMail
|
||||
module VERSION
|
||||
MAJOR = 1
|
||||
MINOR = 2
|
||||
TINY = 3
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
end
|
||||
18
actionmailer/lib/action_mailer/vendor/tmail.rb
vendored
18
actionmailer/lib/action_mailer/vendor/tmail.rb
vendored
@@ -1,18 +0,0 @@
|
||||
# Prefer gems to the bundled libs.
|
||||
require 'rubygems'
|
||||
|
||||
begin
|
||||
gem 'tmail', '~> 1.2.3'
|
||||
rescue Gem::LoadError
|
||||
$:.unshift "#{File.dirname(__FILE__)}/tmail-1.2.3"
|
||||
end
|
||||
|
||||
module TMail
|
||||
end
|
||||
|
||||
require 'tmail'
|
||||
|
||||
require 'active_support/core_ext/kernel/reporting'
|
||||
silence_warnings do
|
||||
TMail::Encoder.const_set("MAX_LINE_LEN", 200)
|
||||
end
|
||||
@@ -24,7 +24,7 @@ class AssetHostTest < Test::Unit::TestCase
|
||||
def test_asset_host_as_string
|
||||
ActionController::Base.asset_host = "http://www.example.com"
|
||||
mail = AssetHostMailer.deliver_email_with_asset(@recipient)
|
||||
assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.strip
|
||||
assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_asset_host_as_one_arguement_proc
|
||||
@@ -36,7 +36,7 @@ class AssetHostTest < Test::Unit::TestCase
|
||||
end
|
||||
}
|
||||
mail = AssetHostMailer.deliver_email_with_asset(@recipient)
|
||||
assert_equal "<img alt=\"Somelogo\" src=\"http://images.example.com/images/somelogo.png\" />", mail.body.strip
|
||||
assert_equal "<img alt=\"Somelogo\" src=\"http://images.example.com/images/somelogo.png\" />", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_asset_host_as_two_arguement_proc
|
||||
@@ -49,6 +49,6 @@ class AssetHostTest < Test::Unit::TestCase
|
||||
}
|
||||
mail = nil
|
||||
assert_nothing_raised { mail = AssetHostMailer.deliver_email_with_asset(@recipient) }
|
||||
assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.strip
|
||||
assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.to_s.strip
|
||||
end
|
||||
end
|
||||
BIN
actionmailer/test/fixtures/attachments/foo.jpg
vendored
Normal file
BIN
actionmailer/test/fixtures/attachments/foo.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
actionmailer/test/fixtures/attachments/test.jpg
vendored
Normal file
BIN
actionmailer/test/fixtures/attachments/test.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
@@ -55,7 +55,7 @@ end
|
||||
|
||||
class MailerHelperTest < Test::Unit::TestCase
|
||||
def new_mail( charset="utf-8" )
|
||||
mail = TMail::Mail.new
|
||||
mail = Mail.new
|
||||
mail.set_content_type "text", "plain", { "charset" => charset } if charset
|
||||
mail
|
||||
end
|
||||
@@ -90,7 +90,7 @@ class MailerHelperTest < Test::Unit::TestCase
|
||||
def test_use_mail_helper
|
||||
mail = HelperMailer.create_use_mail_helper(@recipient)
|
||||
assert_match %r{ But soft!}, mail.encoded
|
||||
assert_match %r{east, and\n Juliet}, mail.encoded
|
||||
assert_match %r{east, and\r\n Juliet}, mail.encoded
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -65,63 +65,85 @@ class LayoutMailerTest < Test::Unit::TestCase
|
||||
|
||||
def test_should_pickup_default_layout
|
||||
mail = AutoLayoutMailer.create_hello(@recipient)
|
||||
assert_equal "Hello from layout Inside", mail.body.strip
|
||||
assert_equal "Hello from layout Inside", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_should_pickup_multipart_layout
|
||||
mail = AutoLayoutMailer.create_multipart(@recipient)
|
||||
assert_equal "multipart/alternative", mail.content_type
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal "multipart/alternative", mail.content_type
|
||||
assert_equal "multipart/alternative", mail.content_type.string
|
||||
assert_equal 2, mail.parts.size
|
||||
|
||||
assert_equal 'text/plain', mail.parts.first.content_type
|
||||
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal 'text/plain', mail.parts.first.content_type
|
||||
assert_equal 'text/plain', mail.parts.first.content_type.string
|
||||
|
||||
# CHANGED: body returns an object
|
||||
# assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
|
||||
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s
|
||||
|
||||
assert_equal 'text/html', mail.parts.last.content_type
|
||||
assert_equal "Hello from layout text/html multipart", mail.parts.last.body
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal 'text/html', mail.parts.last.content_type
|
||||
assert_equal 'text/html', mail.parts.last.content_type.string
|
||||
|
||||
# CHANGED: body returns an object
|
||||
# assert_equal "Hello from layout text/html multipart", mail.parts.last.body
|
||||
assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s
|
||||
end
|
||||
|
||||
def test_should_pickup_multipartmixed_layout
|
||||
mail = AutoLayoutMailer.create_multipart(@recipient, "multipart/mixed")
|
||||
assert_equal "multipart/mixed", mail.content_type
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal "multipart/mixed", mail.content_type
|
||||
assert_equal "multipart/mixed", mail.content_type.string
|
||||
assert_equal 2, mail.parts.size
|
||||
|
||||
assert_equal 'text/plain', mail.parts.first.content_type
|
||||
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal 'text/plain', mail.parts.first.content_type
|
||||
assert_equal 'text/plain', mail.parts.first.content_type.string
|
||||
# CHANGED: body returns an object
|
||||
# assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
|
||||
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s
|
||||
|
||||
assert_equal 'text/html', mail.parts.last.content_type
|
||||
assert_equal "Hello from layout text/html multipart", mail.parts.last.body
|
||||
# CHANGED: content_type returns an object
|
||||
# assert_equal 'text/html', mail.parts.last.content_type
|
||||
assert_equal 'text/html', mail.parts.last.content_type.string
|
||||
# CHANGED: body returns an object
|
||||
# assert_equal "Hello from layout text/html multipart", mail.parts.last.body
|
||||
assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s
|
||||
end
|
||||
|
||||
def test_should_fix_multipart_layout
|
||||
mail = AutoLayoutMailer.create_multipart(@recipient, "text/plain")
|
||||
assert_equal "multipart/alternative", mail.content_type
|
||||
assert_equal "multipart/alternative", mail.content_type.string
|
||||
assert_equal 2, mail.parts.size
|
||||
|
||||
assert_equal 'text/plain', mail.parts.first.content_type
|
||||
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
|
||||
assert_equal 'text/plain', mail.parts.first.content_type.string
|
||||
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s
|
||||
|
||||
assert_equal 'text/html', mail.parts.last.content_type
|
||||
assert_equal "Hello from layout text/html multipart", mail.parts.last.body
|
||||
assert_equal 'text/html', mail.parts.last.content_type.string
|
||||
assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s
|
||||
end
|
||||
|
||||
|
||||
def test_should_pickup_layout_given_to_render
|
||||
mail = AutoLayoutMailer.create_spam(@recipient)
|
||||
assert_equal "Spammer layout Hello, Earth", mail.body.strip
|
||||
assert_equal "Spammer layout Hello, Earth", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_should_respect_layout_false
|
||||
mail = AutoLayoutMailer.create_nolayout(@recipient)
|
||||
assert_equal "Hello, Earth", mail.body.strip
|
||||
assert_equal "Hello, Earth", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_explicit_class_layout
|
||||
mail = ExplicitLayoutMailer.create_signup(@recipient)
|
||||
assert_equal "Spammer layout We do not spam", mail.body.strip
|
||||
assert_equal "Spammer layout We do not spam", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_explicit_layout_exceptions
|
||||
mail = ExplicitLayoutMailer.create_logout(@recipient)
|
||||
assert_equal "You logged out", mail.body.strip
|
||||
assert_equal "You logged out", mail.body.to_s.strip
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,7 +53,9 @@ class RenderMailer < ActionMailer::Base
|
||||
subject "No Instance Variable"
|
||||
from "tester@example.com"
|
||||
|
||||
render :inline => "Look, subject.nil? is <%= @subject.nil? %>!"
|
||||
silence_warnings do
|
||||
render :inline => "Look, subject.nil? is <%= @subject.nil? %>!"
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_defaults(method_name)
|
||||
@@ -78,6 +80,8 @@ class SecondMailer < ActionMailer::Base
|
||||
end
|
||||
end
|
||||
|
||||
# CHANGED: Those tests were changed because body returns an object now
|
||||
# Instead of mail.body.strip, we should mail.body.to_s.strip
|
||||
class RenderHelperTest < Test::Unit::TestCase
|
||||
def setup
|
||||
set_delivery_method :test
|
||||
@@ -93,37 +97,37 @@ class RenderHelperTest < Test::Unit::TestCase
|
||||
|
||||
def test_implicit_body
|
||||
mail = RenderMailer.create_implicit_body(@recipient)
|
||||
assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip
|
||||
assert_equal "Hello there, \n\nMr. test@localhost", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_inline_template
|
||||
mail = RenderMailer.create_inline_template(@recipient)
|
||||
assert_equal "Hello, Earth", mail.body.strip
|
||||
assert_equal "Hello, Earth", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_file_template
|
||||
mail = RenderMailer.create_file_template(@recipient)
|
||||
assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip
|
||||
assert_equal "Hello there, \n\nMr. test@localhost", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_rxml_template
|
||||
mail = RenderMailer.deliver_rxml_template(@recipient)
|
||||
assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<test/>", mail.body.strip
|
||||
assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<test/>", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_included_subtemplate
|
||||
mail = RenderMailer.deliver_included_subtemplate(@recipient)
|
||||
assert_equal "Hey Ho, let's go!", mail.body.strip
|
||||
assert_equal "Hey Ho, let's go!", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_mailer_accessor
|
||||
mail = RenderMailer.deliver_mailer_accessor(@recipient)
|
||||
assert_equal "Look, Mailer Accessor!", mail.body.strip
|
||||
assert_equal "Look, Mailer Accessor!", mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_no_instance_variable
|
||||
mail = RenderMailer.deliver_no_instance_variable(@recipient)
|
||||
assert_equal "Look, subject.nil? is true!", mail.body.strip
|
||||
assert_equal "Look, subject.nil? is true!", mail.body.to_s.strip
|
||||
end
|
||||
end
|
||||
|
||||
@@ -142,12 +146,12 @@ class FirstSecondHelperTest < Test::Unit::TestCase
|
||||
|
||||
def test_ordering
|
||||
mail = FirstMailer.create_share(@recipient)
|
||||
assert_equal "first mail", mail.body.strip
|
||||
assert_equal "first mail", mail.body.to_s.strip
|
||||
mail = SecondMailer.create_share(@recipient)
|
||||
assert_equal "second mail", mail.body.strip
|
||||
assert_equal "second mail", mail.body.to_s.strip
|
||||
mail = FirstMailer.create_share(@recipient)
|
||||
assert_equal "first mail", mail.body.strip
|
||||
assert_equal "first mail", mail.body.to_s.strip
|
||||
mail = SecondMailer.create_share(@recipient)
|
||||
assert_equal "second mail", mail.body.strip
|
||||
assert_equal "second mail", mail.body.to_s.strip
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,7 @@ class FunkyPathMailer < ActionMailer::Base
|
||||
subject "This path has dots"
|
||||
from "Chad Fowler <chad@chadfowler.com>"
|
||||
attachment :content_type => "text/plain",
|
||||
:body => "dots dots dots..."
|
||||
:data => "dots dots dots..."
|
||||
end
|
||||
end
|
||||
|
||||
@@ -107,7 +107,7 @@ class TestMailer < ActionMailer::Base
|
||||
cc "Foo áëô îü <extended@example.net>"
|
||||
bcc "Foo áëô îü <extended@example.net>"
|
||||
charset "utf-8"
|
||||
|
||||
|
||||
render :text => "åœö blah"
|
||||
end
|
||||
|
||||
@@ -155,8 +155,8 @@ class TestMailer < ActionMailer::Base
|
||||
p.body = "blah"
|
||||
end
|
||||
|
||||
attachment :content_type => "image/jpeg", :filename => "foo.jpg",
|
||||
:body => "123456789"
|
||||
attachment :content_type => "image/jpeg", :filename => File.join(File.dirname(__FILE__), "fixtures", "attachments", "foo.jpg"),
|
||||
:data => "123456789"
|
||||
|
||||
render :text => "plain text default"
|
||||
end
|
||||
@@ -239,12 +239,12 @@ class TestMailer < ActionMailer::Base
|
||||
from "test@example.com"
|
||||
content_type "multipart/mixed"
|
||||
|
||||
part :content_type => "multipart/alternative", :content_disposition => "inline", :headers => { "foo" => "bar" } do |p|
|
||||
part :content_type => "multipart/alternative", :content_disposition => "inline", "foo" => "bar" do |p|
|
||||
p.part :content_type => "text/plain", :body => "test text\nline #2"
|
||||
p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2"
|
||||
end
|
||||
|
||||
attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz"
|
||||
attachment :content_type => "application/octet-stream", :filename => "test.txt", :data => "test abcdefghijklmnopqstuvwxyz"
|
||||
end
|
||||
|
||||
def nested_multipart_with_body(recipient)
|
||||
@@ -263,8 +263,8 @@ class TestMailer < ActionMailer::Base
|
||||
subject "custom header in attachment"
|
||||
from "test@example.com"
|
||||
content_type "multipart/related"
|
||||
part :content_type => "text/html", :body => 'yo'
|
||||
attachment :content_type => "image/jpeg",:filename => "test.jpeg", :body => "i am not a real picture", :headers => { 'Content-ID' => '<test@test.com>' }
|
||||
part :content_type => "text/html", :body => 'yo'
|
||||
attachment :content_type => "image/jpeg", :filename => File.join(File.dirname(__FILE__), "fixtures", "attachments", "test.jpg"), :data => "i am not a real picture", 'Content-ID' => '<test@test.com>'
|
||||
end
|
||||
|
||||
def unnamed_attachment(recipient)
|
||||
@@ -273,7 +273,7 @@ class TestMailer < ActionMailer::Base
|
||||
from "test@example.com"
|
||||
content_type "multipart/mixed"
|
||||
part :content_type => "text/plain", :body => "hullo"
|
||||
attachment :content_type => "application/octet-stream", :body => "test abcdefghijklmnopqstuvwxyz"
|
||||
attachment :content_type => "application/octet-stream", :data => "test abcdefghijklmnopqstuvwxyz"
|
||||
end
|
||||
|
||||
def headers_with_nonalpha_chars(recipient)
|
||||
@@ -332,10 +332,10 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def new_mail( charset="utf-8" )
|
||||
mail = TMail::Mail.new
|
||||
mail = Mail.new
|
||||
mail.mime_version = "1.0"
|
||||
if charset
|
||||
mail.set_content_type "text", "plain", { "charset" => charset }
|
||||
mail.content_type ["text", "plain", { "charset" => charset }]
|
||||
end
|
||||
mail
|
||||
end
|
||||
@@ -359,30 +359,33 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
def test_nested_parts
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.create_nested_multipart(@recipient)}
|
||||
assert_equal 2,created.parts.size
|
||||
assert_equal 2,created.parts.first.parts.size
|
||||
assert_equal 2, created.parts.size
|
||||
assert_equal 2, created.parts.first.parts.size
|
||||
|
||||
assert_equal "multipart/mixed", created.content_type
|
||||
assert_equal "multipart/alternative", created.parts.first.content_type
|
||||
assert_equal "bar", created.parts.first.header['foo'].to_s
|
||||
assert_nil created.parts.first.charset
|
||||
assert_equal "text/plain", created.parts.first.parts.first.content_type
|
||||
assert_equal "text/html", created.parts.first.parts[1].content_type
|
||||
assert_equal "application/octet-stream", created.parts[1].content_type
|
||||
assert_equal "multipart/mixed", created.content_type.string
|
||||
assert_equal "multipart/alternative", created.parts[0].content_type.string
|
||||
assert_equal "bar", created.parts[0].header['foo'].to_s
|
||||
assert_nil created.parts[0].charset
|
||||
assert_equal "text/plain", created.parts[0].parts[0].content_type.string
|
||||
assert_equal "text/html", created.parts[0].parts[1].content_type.string
|
||||
assert_equal "application/octet-stream", created.parts[1].content_type.string
|
||||
|
||||
end
|
||||
|
||||
def test_nested_parts_with_body
|
||||
created = nil
|
||||
TestMailer.create_nested_multipart_with_body(@recipient)
|
||||
assert_nothing_raised { created = TestMailer.create_nested_multipart_with_body(@recipient)}
|
||||
|
||||
assert_equal 1,created.parts.size
|
||||
assert_equal 2,created.parts.first.parts.size
|
||||
|
||||
assert_equal "multipart/mixed", created.content_type
|
||||
assert_equal "multipart/alternative", created.parts.first.content_type
|
||||
assert_equal "Nothing to see here.", created.parts.first.parts.first.body
|
||||
assert_equal "text/plain", created.parts.first.parts.first.content_type
|
||||
assert_equal "text/html", created.parts.first.parts[1].content_type
|
||||
assert_equal "multipart/mixed", created.content_type.string
|
||||
assert_equal "multipart/alternative", created.parts.first.content_type.string
|
||||
assert_equal "text/plain", created.parts.first.parts.first.content_type.string
|
||||
assert_equal "Nothing to see here.", created.parts.first.parts.first.body.to_s
|
||||
assert_equal "text/html", created.parts.first.parts.second.content_type.string
|
||||
assert_equal "<b>test</b> HTML<br/>", created.parts.first.parts.second.body.to_s
|
||||
end
|
||||
|
||||
def test_attachment_with_custom_header
|
||||
@@ -404,20 +407,30 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
|
||||
assert_not_nil created
|
||||
|
||||
expected.message_id = '<123@456>'
|
||||
created.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, created.encoded
|
||||
|
||||
assert_nothing_raised { TestMailer.deliver_signed_up(@recipient) }
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
|
||||
delivered = ActionMailer::Base.deliveries.first
|
||||
assert_not_nil delivered
|
||||
|
||||
expected.message_id = '<123@456>'
|
||||
delivered.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, delivered.encoded
|
||||
end
|
||||
|
||||
def test_subject_with_i18n
|
||||
assert_nothing_raised { TestMailer.deliver_subject_with_i18n(@recipient) }
|
||||
assert_equal "Subject with i18n", ActionMailer::Base.deliveries.first.subject
|
||||
assert_equal "Subject with i18n", ActionMailer::Base.deliveries.first.subject.to_s
|
||||
|
||||
I18n.backend.store_translations('en', :actionmailer => {:test_mailer => {:subject_with_i18n => {:subject => "New Subject!"}}})
|
||||
assert_nothing_raised { TestMailer.deliver_subject_with_i18n(@recipient) }
|
||||
assert_equal "New Subject!", ActionMailer::Base.deliveries.last.subject
|
||||
assert_equal "New Subject!", ActionMailer::Base.deliveries.last.subject.to_s
|
||||
end
|
||||
|
||||
def test_custom_template
|
||||
@@ -431,6 +444,8 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.create_custom_template(@recipient) }
|
||||
assert_not_nil created
|
||||
expected.message_id = '<123@456>'
|
||||
created.message_id = '<123@456>'
|
||||
assert_equal expected.encoded, created.encoded
|
||||
end
|
||||
|
||||
@@ -453,8 +468,8 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
assert_nothing_raised { created = TestMailer.create_custom_templating_extension(@recipient) }
|
||||
assert_not_nil created
|
||||
assert_equal 2, created.parts.length
|
||||
assert_equal 'text/plain', created.parts[0].content_type
|
||||
assert_equal 'text/html', created.parts[1].content_type
|
||||
assert_equal 'text/plain', created.parts[0].content_type.string
|
||||
assert_equal 'text/html', created.parts[1].content_type.string
|
||||
end
|
||||
|
||||
def test_cancelled_account
|
||||
@@ -468,11 +483,17 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.create_cancelled_account(@recipient) }
|
||||
assert_not_nil created
|
||||
expected.message_id = '<123@456>'
|
||||
created.message_id = '<123@456>'
|
||||
assert_equal expected.encoded, created.encoded
|
||||
|
||||
assert_nothing_raised { TestMailer.deliver_cancelled_account(@recipient) }
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
delivered = ActionMailer::Base.deliveries.first
|
||||
expected.message_id = '<123@456>'
|
||||
delivered.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, delivered.encoded
|
||||
end
|
||||
|
||||
def test_cc_bcc
|
||||
@@ -490,6 +511,8 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
created = TestMailer.create_cc_bcc @recipient
|
||||
end
|
||||
assert_not_nil created
|
||||
expected.message_id = '<123@456>'
|
||||
created.message_id = '<123@456>'
|
||||
assert_equal expected.encoded, created.encoded
|
||||
|
||||
assert_nothing_raised do
|
||||
@@ -497,7 +520,11 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
delivered = ActionMailer::Base.deliveries.first
|
||||
expected.message_id = '<123@456>'
|
||||
delivered.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, delivered.encoded
|
||||
end
|
||||
|
||||
def test_from_without_name_for_smtp
|
||||
@@ -519,7 +546,7 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
assert_not_nil mail
|
||||
mail, from, to = mail
|
||||
|
||||
assert_equal 'system@loudthinking.com', from.to_s
|
||||
assert_equal 'system@loudthinking.com', from.addresses.first
|
||||
end
|
||||
|
||||
def test_reply_to
|
||||
@@ -537,14 +564,23 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
created = TestMailer.create_different_reply_to @recipient
|
||||
end
|
||||
assert_not_nil created
|
||||
|
||||
expected.message_id = '<123@456>'
|
||||
created.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, created.encoded
|
||||
|
||||
assert_nothing_raised do
|
||||
TestMailer.deliver_different_reply_to @recipient
|
||||
end
|
||||
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
delivered = ActionMailer::Base.deliveries.first
|
||||
assert_not_nil delivered
|
||||
|
||||
expected.message_id = '<123@456>'
|
||||
delivered.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, delivered.encoded
|
||||
end
|
||||
|
||||
def test_iso_charset
|
||||
@@ -562,14 +598,23 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
created = TestMailer.create_iso_charset @recipient
|
||||
end
|
||||
assert_not_nil created
|
||||
|
||||
expected.message_id = '<123@456>'
|
||||
created.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, created.encoded
|
||||
|
||||
assert_nothing_raised do
|
||||
TestMailer.deliver_iso_charset @recipient
|
||||
end
|
||||
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
delivered = ActionMailer::Base.deliveries.first
|
||||
assert_not_nil delivered
|
||||
|
||||
expected.message_id = '<123@456>'
|
||||
delivered.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, delivered.encoded
|
||||
end
|
||||
|
||||
def test_unencoded_subject
|
||||
@@ -587,14 +632,23 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
created = TestMailer.create_unencoded_subject @recipient
|
||||
end
|
||||
assert_not_nil created
|
||||
|
||||
expected.message_id = '<123@456>'
|
||||
created.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, created.encoded
|
||||
|
||||
assert_nothing_raised do
|
||||
TestMailer.deliver_unencoded_subject @recipient
|
||||
end
|
||||
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
delivered = ActionMailer::Base.deliveries.first
|
||||
assert_not_nil delivered
|
||||
|
||||
expected.message_id = '<123@456>'
|
||||
delivered.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, delivered.encoded
|
||||
end
|
||||
|
||||
def test_instances_are_nil
|
||||
@@ -660,7 +714,12 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||
TestMailer.logger = FakeLogger.new
|
||||
TestMailer.deliver_signed_up(@recipient)
|
||||
assert(TestMailer.logger.info_contents =~ /Sent mail to #{@recipient}/)
|
||||
assert_equal(TestMailer.logger.debug_contents, "\n#{mail.encoded}")
|
||||
expected = TestMailer.logger.debug_contents
|
||||
actual = "\n#{mail.encoded}"
|
||||
expected.gsub!(/Message-ID:.*\r\n/, "Message-ID: <123@456>\r\n")
|
||||
actual.gsub!(/Message-ID:.*\r\n/, "Message-ID: <123@456>\r\n")
|
||||
|
||||
assert_equal(expected, actual)
|
||||
end
|
||||
|
||||
def test_unquote_quoted_printable_subject
|
||||
@@ -671,9 +730,9 @@ Content-Type: text/plain; charset=iso-8859-1
|
||||
|
||||
The body
|
||||
EOF
|
||||
mail = TMail::Mail.parse(msg)
|
||||
assert_equal "testing testing \326\244", mail.subject
|
||||
assert_equal "=?utf-8?Q?testing_testing_=D6=A4?=", mail.quoted_subject
|
||||
mail = Mail.new(msg)
|
||||
assert_equal "testing testing \326\244", mail.subject.to_s
|
||||
assert_equal "Subject: =?utf-8?Q?testing_testing_=D6=A4?=\r\n", mail.subject.encoded
|
||||
end
|
||||
|
||||
def test_unquote_7bit_subject
|
||||
@@ -684,9 +743,9 @@ Content-Type: text/plain; charset=iso-8859-1
|
||||
|
||||
The body
|
||||
EOF
|
||||
mail = TMail::Mail.parse(msg)
|
||||
assert_equal "this == working?", mail.subject
|
||||
assert_equal "this == working?", mail.quoted_subject
|
||||
mail = Mail.new(msg)
|
||||
assert_equal "this == working?", mail.subject.to_s
|
||||
assert_equal "Subject: this == working?\r\n", mail.subject.encoded
|
||||
end
|
||||
|
||||
def test_unquote_7bit_body
|
||||
@@ -698,9 +757,9 @@ Content-Transfer-Encoding: 7bit
|
||||
|
||||
The=3Dbody
|
||||
EOF
|
||||
mail = TMail::Mail.parse(msg)
|
||||
assert_equal "The=3Dbody", mail.body.strip
|
||||
assert_equal "The=3Dbody", mail.quoted_body.strip
|
||||
mail = Mail.new(msg)
|
||||
assert_equal "The=3Dbody", mail.body.to_s.strip
|
||||
assert_equal "The=3Dbody", mail.body.encoded.strip
|
||||
end
|
||||
|
||||
def test_unquote_quoted_printable_body
|
||||
@@ -712,9 +771,9 @@ Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
The=3Dbody
|
||||
EOF
|
||||
mail = TMail::Mail.parse(msg)
|
||||
assert_equal "The=body", mail.body.strip
|
||||
assert_equal "The=3Dbody", mail.quoted_body.strip
|
||||
mail = Mail.new(msg)
|
||||
assert_equal "The=body", mail.body.to_s.strip
|
||||
assert_equal "The=3Dbody", mail.body.encoded.strip
|
||||
end
|
||||
|
||||
def test_unquote_base64_body
|
||||
@@ -726,9 +785,9 @@ Content-Transfer-Encoding: base64
|
||||
|
||||
VGhlIGJvZHk=
|
||||
EOF
|
||||
mail = TMail::Mail.parse(msg)
|
||||
assert_equal "The body", mail.body.strip
|
||||
assert_equal "VGhlIGJvZHk=", mail.quoted_body.strip
|
||||
mail = Mail.new(msg)
|
||||
assert_equal "The body", mail.body.to_s.strip
|
||||
assert_equal "VGhlIGJvZHk=", mail.body.encoded.strip
|
||||
end
|
||||
|
||||
def test_extended_headers
|
||||
@@ -749,14 +808,22 @@ EOF
|
||||
end
|
||||
|
||||
assert_not_nil created
|
||||
expected.message_id = '<123@456>'
|
||||
created.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, created.encoded
|
||||
|
||||
assert_nothing_raised do
|
||||
TestMailer.deliver_extended_headers @recipient
|
||||
end
|
||||
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
delivered = ActionMailer::Base.deliveries.first
|
||||
assert_not_nil delivered
|
||||
|
||||
expected.message_id = '<123@456>'
|
||||
delivered.message_id = '<123@456>'
|
||||
|
||||
assert_equal expected.encoded, delivered.encoded
|
||||
end
|
||||
|
||||
def test_utf8_body_is_not_quoted
|
||||
@@ -787,40 +854,40 @@ EOF
|
||||
|
||||
created = TestMailer.create_utf8_body @recipient
|
||||
assert_match(/\nFrom: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>\r/, created.encoded)
|
||||
assert_match(/\nTo: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>, Example Recipient <me/, created.encoded)
|
||||
assert_match(/\nTo: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>, \r\n\tExample Recipient <me/, created.encoded)
|
||||
end
|
||||
|
||||
def test_receive_decodes_base64_encoded_mail
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email")
|
||||
TestMailer.receive(fixture)
|
||||
assert_match(/Jamis/, TestMailer.received_body)
|
||||
assert_match(/Jamis/, TestMailer.received_body.to_s)
|
||||
end
|
||||
|
||||
def test_receive_attachments
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email2")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
mail = Mail.new(fixture)
|
||||
attachment = mail.attachments.last
|
||||
assert_equal "smime.p7s", attachment.original_filename
|
||||
assert_equal "application/pkcs7-signature", attachment.content_type
|
||||
assert_equal "application/pkcs7-signature", mail.parts.last.content_type.string
|
||||
end
|
||||
|
||||
def test_decode_attachment_without_charset
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email3")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
mail = Mail.new(fixture)
|
||||
attachment = mail.attachments.last
|
||||
assert_equal 1026, attachment.read.length
|
||||
end
|
||||
|
||||
def test_attachment_using_content_location
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email12")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
mail = Mail.new(fixture)
|
||||
assert_equal 1, mail.attachments.length
|
||||
assert_equal "Photo25.jpg", mail.attachments.first.original_filename
|
||||
end
|
||||
|
||||
def test_attachment_with_text_type
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email13")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
mail = Mail.new(fixture)
|
||||
assert mail.has_attachments?
|
||||
assert_equal 1, mail.attachments.length
|
||||
assert_equal "hello.rb", mail.attachments.first.original_filename
|
||||
@@ -828,25 +895,25 @@ EOF
|
||||
|
||||
def test_decode_part_without_content_type
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email4")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
mail = Mail.new(fixture)
|
||||
assert_nothing_raised { mail.body }
|
||||
end
|
||||
|
||||
def test_decode_message_without_content_type
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email5")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
mail = Mail.new(fixture)
|
||||
assert_nothing_raised { mail.body }
|
||||
end
|
||||
|
||||
def test_decode_message_with_incorrect_charset
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email6")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
mail = Mail.new(fixture)
|
||||
assert_nothing_raised { mail.body }
|
||||
end
|
||||
|
||||
def test_multipart_with_mime_version
|
||||
mail = TestMailer.create_multipart_with_mime_version(@recipient)
|
||||
assert_equal "1.1", mail.mime_version
|
||||
assert_equal "1.1", mail.mime_version.version
|
||||
end
|
||||
|
||||
def test_multipart_with_utf8_subject
|
||||
@@ -862,30 +929,29 @@ EOF
|
||||
def test_explicitly_multipart_messages
|
||||
mail = TestMailer.create_explicitly_multipart_example(@recipient)
|
||||
assert_equal 3, mail.parts.length
|
||||
assert_nil mail.content_type
|
||||
assert_equal "text/plain", mail.parts[0].content_type
|
||||
assert_equal 'multipart/mixed', mail.content_type.string
|
||||
assert_equal "text/plain", mail.parts[0].content_type.string
|
||||
|
||||
assert_equal "text/html", mail.parts[1].content_type
|
||||
assert_equal "iso-8859-1", mail.parts[1].sub_header("content-type", "charset")
|
||||
assert_equal "inline", mail.parts[1].content_disposition
|
||||
assert_equal "text/html", mail.parts[1].content_type.string
|
||||
assert_equal "iso-8859-1", mail.parts[1].charset
|
||||
|
||||
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")
|
||||
assert_nil mail.parts[2].sub_header("content-type", "charset")
|
||||
assert_equal "image/jpeg", mail.parts[2].content_type.string
|
||||
assert_equal "attachment", mail.parts[2].content_disposition.disposition_type
|
||||
assert_equal "foo.jpg", mail.parts[2].content_disposition.filename
|
||||
assert_equal "foo.jpg", mail.parts[2].content_type.filename
|
||||
assert_nil mail.parts[2].charset
|
||||
end
|
||||
|
||||
def test_explicitly_multipart_with_content_type
|
||||
mail = TestMailer.create_explicitly_multipart_example(@recipient, "multipart/alternative")
|
||||
assert_equal 3, mail.parts.length
|
||||
assert_equal "multipart/alternative", mail.content_type
|
||||
assert_equal "multipart/alternative", mail.content_type.string
|
||||
end
|
||||
|
||||
def test_explicitly_multipart_with_invalid_content_type
|
||||
mail = TestMailer.create_explicitly_multipart_example(@recipient, "text/xml")
|
||||
assert_equal 3, mail.parts.length
|
||||
assert_nil mail.content_type
|
||||
assert_equal 'multipart/mixed', mail.content_type.string
|
||||
end
|
||||
|
||||
def test_implicitly_multipart_messages
|
||||
@@ -893,14 +959,14 @@ EOF
|
||||
|
||||
mail = TestMailer.create_implicitly_multipart_example(@recipient)
|
||||
assert_equal 3, mail.parts.length
|
||||
assert_equal "1.0", mail.mime_version
|
||||
assert_equal "multipart/alternative", mail.content_type
|
||||
assert_equal "application/x-yaml", mail.parts[0].content_type
|
||||
assert_equal "utf-8", mail.parts[0].sub_header("content-type", "charset")
|
||||
assert_equal "text/plain", mail.parts[1].content_type
|
||||
assert_equal "utf-8", mail.parts[1].sub_header("content-type", "charset")
|
||||
assert_equal "text/html", mail.parts[2].content_type
|
||||
assert_equal "utf-8", mail.parts[2].sub_header("content-type", "charset")
|
||||
assert_equal "1.0", mail.mime_version.to_s
|
||||
assert_equal "multipart/alternative", mail.content_type.string
|
||||
assert_equal "text/plain", mail.parts[0].content_type.string
|
||||
assert_equal "utf-8", mail.parts[0].charset
|
||||
assert_equal "text/html", mail.parts[1].content_type.string
|
||||
assert_equal "utf-8", mail.parts[1].charset
|
||||
assert_equal "application/x-yaml", mail.parts[2].content_type.string
|
||||
assert_equal "utf-8", mail.parts[2].charset
|
||||
end
|
||||
|
||||
def test_implicitly_multipart_messages_with_custom_order
|
||||
@@ -908,41 +974,43 @@ EOF
|
||||
|
||||
mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["application/x-yaml", "text/plain"])
|
||||
assert_equal 3, mail.parts.length
|
||||
assert_equal "text/html", mail.parts[0].content_type
|
||||
assert_equal "text/plain", mail.parts[1].content_type
|
||||
assert_equal "application/x-yaml", mail.parts[2].content_type
|
||||
assert_equal "application/x-yaml", mail.parts[0].content_type.string
|
||||
assert_equal "text/plain", mail.parts[1].content_type.string
|
||||
assert_equal "text/html", mail.parts[2].content_type.string
|
||||
end
|
||||
|
||||
def test_implicitly_multipart_messages_with_charset
|
||||
mail = TestMailer.create_implicitly_multipart_example(@recipient, 'iso-8859-1')
|
||||
|
||||
assert_equal "multipart/alternative", mail.header['content-type'].body
|
||||
assert_equal "multipart/alternative", mail.header['content-type'].content_type
|
||||
|
||||
assert_equal 'iso-8859-1', mail.parts[0].sub_header("content-type", "charset")
|
||||
assert_equal 'iso-8859-1', mail.parts[1].sub_header("content-type", "charset")
|
||||
assert_equal 'iso-8859-1', mail.parts[2].sub_header("content-type", "charset")
|
||||
assert_equal 'iso-8859-1', mail.parts[0].content_type.parameters[:charset]
|
||||
assert_equal 'iso-8859-1', mail.parts[1].content_type.parameters[:charset]
|
||||
assert_equal 'iso-8859-1', mail.parts[2].content_type.parameters[:charset]
|
||||
end
|
||||
|
||||
def test_html_mail
|
||||
mail = TestMailer.create_html_mail(@recipient)
|
||||
assert_equal "text/html", mail.content_type
|
||||
assert_equal "text/html", mail.content_type.string
|
||||
end
|
||||
|
||||
def test_html_mail_with_underscores
|
||||
mail = TestMailer.create_html_mail_with_underscores(@recipient)
|
||||
assert_equal %{<a href="http://google.com" target="_blank">_Google</a>}, mail.body
|
||||
assert_equal %{<a href="http://google.com" target="_blank">_Google</a>}, mail.body.to_s
|
||||
end
|
||||
|
||||
def test_various_newlines
|
||||
mail = TestMailer.create_various_newlines(@recipient)
|
||||
assert_equal("line #1\nline #2\nline #3\nline #4\n\n" +
|
||||
"line #5\n\nline#6\n\nline #7", mail.body)
|
||||
"line #5\n\nline#6\n\nline #7", mail.body.to_s)
|
||||
end
|
||||
|
||||
def test_various_newlines_multipart
|
||||
mail = TestMailer.create_various_newlines_multipart(@recipient)
|
||||
assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body
|
||||
assert_equal "<p>line #1</p>\n<p>line #2</p>\n<p>line #3</p>\n<p>line #4</p>\n\n", mail.parts[1].body
|
||||
assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body.to_s
|
||||
assert_equal "<p>line #1</p>\n<p>line #2</p>\n<p>line #3</p>\n<p>line #4</p>\n\n", mail.parts[1].body.to_s
|
||||
assert_equal "line #1\r\nline #2\r\nline #3\r\nline #4\r\n\r\n", mail.parts[0].body.encoded
|
||||
assert_equal "<p>line #1</p>\r\n<p>line #2</p>\r\n<p>line #3</p>\r\n<p>line #4</p>\r\n\r\n", mail.parts[1].body.encoded
|
||||
end
|
||||
|
||||
def test_headers_removed_on_smtp_delivery
|
||||
@@ -970,35 +1038,41 @@ EOF
|
||||
|
||||
def test_recursive_multipart_processing
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
assert_equal "This is the first part.\n\nAttachment: test.rb\nAttachment: test.pdf\n\n\nAttachment: smime.p7s\n", mail.body
|
||||
mail = Mail.new(fixture)
|
||||
assert_equal(2, mail.parts.length)
|
||||
assert_equal(4, mail.parts.first.parts.length)
|
||||
assert_equal("This is the first part.", mail.parts.first.parts.first.body.to_s)
|
||||
assert_equal("test.rb", mail.parts.first.parts.second.filename)
|
||||
assert_equal("flowed", mail.parts.first.parts.fourth.content_type.parameters[:format])
|
||||
assert_equal('smime.p7s', mail.parts.second.filename)
|
||||
end
|
||||
|
||||
def test_decode_encoded_attachment_filename
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email8")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
mail = Mail.new(fixture)
|
||||
attachment = mail.attachments.last
|
||||
|
||||
expected = "01 Quien Te Dij\212at. Pitbull.mp3"
|
||||
expected.force_encoding(Encoding::ASCII_8BIT) if expected.respond_to?(:force_encoding)
|
||||
|
||||
assert_equal expected, attachment.original_filename
|
||||
end
|
||||
|
||||
def test_wrong_mail_header
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email9")
|
||||
assert_raise(TMail::SyntaxError) { TMail::Mail.parse(fixture) }
|
||||
|
||||
if expected.respond_to?(:force_encoding)
|
||||
result = attachment.original_filename.dup
|
||||
expected.force_encoding(Encoding::ASCII_8BIT)
|
||||
result.force_encoding(Encoding::ASCII_8BIT)
|
||||
assert_equal expected, result
|
||||
else
|
||||
assert_equal expected, attachment.original_filename
|
||||
end
|
||||
end
|
||||
|
||||
def test_decode_message_with_unknown_charset
|
||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email10")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
mail = Mail.new(fixture)
|
||||
assert_nothing_raised { mail.body }
|
||||
end
|
||||
|
||||
def test_empty_header_values_omitted
|
||||
result = TestMailer.create_unnamed_attachment(@recipient).encoded
|
||||
assert_match %r{Content-Type: application/octet-stream[^;]}, result
|
||||
assert_match %r{Content-Type: application/octet-stream;}, result
|
||||
assert_match %r{Content-Disposition: attachment[^;]}, result
|
||||
end
|
||||
|
||||
@@ -1021,32 +1095,32 @@ EOF
|
||||
def test_multipart_with_template_path_with_dots
|
||||
mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
|
||||
assert_equal 2, mail.parts.length
|
||||
assert "text/plain", mail.parts[1].content_type
|
||||
assert "text/plain", mail.parts[1].content_type.string
|
||||
assert "utf-8", mail.parts[1].charset
|
||||
end
|
||||
|
||||
def test_custom_content_type_attributes
|
||||
mail = TestMailer.create_custom_content_type_attributes
|
||||
assert_match %r{format=flowed}, mail['content-type'].to_s
|
||||
assert_match %r{charset=utf-8}, mail['content-type'].to_s
|
||||
assert_match %r{format="flowed"}, mail.content_type.encoded
|
||||
assert_match %r{charset="utf-8"}, mail.content_type.encoded
|
||||
end
|
||||
|
||||
def test_return_path_with_create
|
||||
mail = TestMailer.create_return_path
|
||||
assert_equal "<another@somewhere.test>", mail['return-path'].to_s
|
||||
assert_equal "another@somewhere.test", mail['return-path'].to_s
|
||||
end
|
||||
|
||||
def test_return_path_with_deliver
|
||||
ActionMailer::Base.delivery_method = :smtp
|
||||
TestMailer.deliver_return_path
|
||||
assert_match %r{^Return-Path: <another@somewhere.test>}, MockSMTP.deliveries[0][0]
|
||||
assert_match %r{^Return-Path: another@somewhere.test}, MockSMTP.deliveries[0][0]
|
||||
assert_equal "another@somewhere.test", MockSMTP.deliveries[0][1].to_s
|
||||
end
|
||||
|
||||
def test_body_is_stored_as_an_ivar
|
||||
mail = nil
|
||||
ActiveSupport::Deprecation.silence { mail = TestMailer.create_body_ivar(@recipient) }
|
||||
assert_equal "body: foo\nbar: baz", mail.body
|
||||
assert_equal "body: foo\nbar: baz", mail.body.to_s
|
||||
end
|
||||
|
||||
def test_starttls_is_enabled_if_supported
|
||||
@@ -1175,6 +1249,6 @@ class RespondToTest < Test::Unit::TestCase
|
||||
RespondToMailer.not_a_method
|
||||
end
|
||||
|
||||
assert_match /undefined method.*not_a_method/, error.message
|
||||
assert_match(/undefined method.*not_a_method/, error.message)
|
||||
end
|
||||
end
|
||||
|
||||
24
actionmailer/test/mail_test.rb
Normal file
24
actionmailer/test/mail_test.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class MailTest < Test::Unit::TestCase
|
||||
def test_body
|
||||
m = Mail.new
|
||||
expected = 'something_with_underscores'
|
||||
m.content_transfer_encoding = 'quoted-printable'
|
||||
quoted_body = [expected].pack('*M')
|
||||
m.body = quoted_body
|
||||
assert_equal "something_with_underscores=\r\n", m.body.encoded
|
||||
# CHANGED: body returns object, not string, Changed m.body to m.body.to_s
|
||||
assert_equal expected, m.body.to_s
|
||||
end
|
||||
|
||||
def test_nested_attachments_are_recognized_correctly
|
||||
fixture = File.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_nested_attachment")
|
||||
mail = Mail.new(fixture)
|
||||
assert_equal 2, mail.attachments.length
|
||||
assert_equal "image/png", mail.attachments.first.mime_type
|
||||
assert_equal 1902, mail.attachments.first.decoded.length
|
||||
assert_equal "application/pkcs7-signature", mail.attachments.last.mime_type
|
||||
end
|
||||
|
||||
end
|
||||
@@ -6,37 +6,37 @@ class QuotingTest < Test::Unit::TestCase
|
||||
# Move some tests from TMAIL here
|
||||
def test_unquote_quoted_printable
|
||||
a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?="
|
||||
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
|
||||
b = Mail::Encodings.unquote_and_convert_to(a, 'utf-8')
|
||||
assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
|
||||
end
|
||||
|
||||
def test_unquote_base64
|
||||
a ="=?ISO-8859-1?B?WzE2NjQxN10gQmVrcuZmdGVsc2UgZnJhIFJlanNlZmViZXI=?="
|
||||
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
|
||||
b = Mail::Encodings.unquote_and_convert_to(a, 'utf-8')
|
||||
assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
|
||||
end
|
||||
|
||||
def test_unquote_without_charset
|
||||
a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber"
|
||||
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
|
||||
b = Mail::Encodings.unquote_and_convert_to(a, 'utf-8')
|
||||
assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b
|
||||
end
|
||||
|
||||
def test_unqoute_multiple
|
||||
a ="=?utf-8?q?Re=3A_=5B12=5D_=23137=3A_Inkonsistente_verwendung_von_=22Hin?==?utf-8?b?enVmw7xnZW4i?="
|
||||
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
|
||||
b = Mail::Encodings.unquote_and_convert_to(a, 'utf-8')
|
||||
assert_equal "Re: [12] #137: Inkonsistente verwendung von \"Hinzuf\303\274gen\"", b
|
||||
end
|
||||
|
||||
def test_unqoute_in_the_middle
|
||||
a ="Re: Photos =?ISO-8859-1?Q?Brosch=FCre_Rand?="
|
||||
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
|
||||
b = Mail::Encodings.unquote_and_convert_to(a, 'utf-8')
|
||||
assert_equal "Re: Photos Brosch\303\274re Rand", b
|
||||
end
|
||||
|
||||
def test_unqoute_iso
|
||||
a ="=?ISO-8859-1?Q?Brosch=FCre_Rand?="
|
||||
b = TMail::Unquoter.unquote_and_convert_to(a, 'iso-8859-1')
|
||||
b = Mail::Encodings.unquote_and_convert_to(a, 'iso-8859-1')
|
||||
expected = "Brosch\374re Rand"
|
||||
expected.force_encoding 'iso-8859-1' if expected.respond_to?(:force_encoding)
|
||||
assert_equal expected, b
|
||||
@@ -50,14 +50,17 @@ class QuotingTest < Test::Unit::TestCase
|
||||
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
||||
if RUBY_VERSION < '1.9'
|
||||
$KCODE = 'u'
|
||||
require 'jcode'
|
||||
end
|
||||
require 'action_mailer/quoting'
|
||||
include ActionMailer::Quoting
|
||||
quoted_printable(#{original.inspect}, "UTF-8")
|
||||
CODE
|
||||
|
||||
unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil)
|
||||
unquoted = Mail::Encodings.unquote_and_convert_to(result, nil)
|
||||
|
||||
unquoted.force_encoding(Encoding::ASCII_8BIT) if unquoted.respond_to?(:force_encoding)
|
||||
original.force_encoding(Encoding::ASCII_8BIT) if original.respond_to?(:force_encoding)
|
||||
|
||||
assert_equal unquoted, original
|
||||
end
|
||||
|
||||
@@ -65,13 +68,17 @@ class QuotingTest < Test::Unit::TestCase
|
||||
# test an email that has been created using \r\n newlines, instead of
|
||||
# \n newlines.
|
||||
def test_email_quoted_with_0d0a
|
||||
mail = TMail::Mail.parse(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_quoted_with_0d0a"))
|
||||
assert_match %r{Elapsed time}, mail.body
|
||||
mail = Mail.new(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_quoted_with_0d0a"))
|
||||
# CHANGED: subject returns an object now
|
||||
# assert_match %r{Elapsed time}, mail.body
|
||||
assert_match %r{Elapsed time}, mail.body.to_s
|
||||
end
|
||||
|
||||
def test_email_with_partially_quoted_subject
|
||||
mail = TMail::Mail.parse(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_partially_quoted_subject"))
|
||||
assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject
|
||||
mail = Mail.new(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_partially_quoted_subject"))
|
||||
# CHANGED: subject returns an object now
|
||||
# assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject
|
||||
assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject.decoded
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -18,9 +18,9 @@ class TestHelperMailerTest < ActionMailer::TestCase
|
||||
end
|
||||
|
||||
def test_setup_creates_the_expected_mailer
|
||||
assert @expected.is_a?(TMail::Mail)
|
||||
assert_equal "1.0", @expected.mime_version
|
||||
assert_equal "text/plain", @expected.content_type
|
||||
assert @expected.is_a?(Mail::Message)
|
||||
assert_equal "1.0", @expected.mime_version.version
|
||||
assert_equal "text/plain", @expected.content_type.string
|
||||
end
|
||||
|
||||
def test_mailer_class_is_correctly_inferred
|
||||
@@ -92,7 +92,7 @@ class TestHelperMailerTest < ActionMailer::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
assert_match /2 .* but 1/, error.message
|
||||
assert_match(/2 .* but 1/, error.message)
|
||||
end
|
||||
|
||||
def test_assert_emails_too_many_sent
|
||||
@@ -103,7 +103,7 @@ class TestHelperMailerTest < ActionMailer::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
assert_match /1 .* but 2/, error.message
|
||||
assert_match(/1 .* but 2/, error.message)
|
||||
end
|
||||
|
||||
def test_assert_no_emails_failure
|
||||
@@ -113,7 +113,7 @@ class TestHelperMailerTest < ActionMailer::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
assert_match /0 .* but 1/, error.message
|
||||
assert_match(/0 .* but 1/, error.message)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -125,7 +125,7 @@ class AnotherTestHelperMailerTest < ActionMailer::TestCase
|
||||
end
|
||||
|
||||
def test_setup_shouldnt_conflict_with_mailer_setup
|
||||
assert @expected.is_a?(TMail::Mail)
|
||||
assert @expected.is_a?(Mail::Message)
|
||||
assert_equal 'a value', @test_var
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class TMailMailTest < Test::Unit::TestCase
|
||||
def test_body
|
||||
m = TMail::Mail.new
|
||||
expected = 'something_with_underscores'
|
||||
m.encoding = 'quoted-printable'
|
||||
quoted_body = [expected].pack('*M')
|
||||
m.body = quoted_body
|
||||
assert_equal "something_with_underscores=\n", m.quoted_body
|
||||
assert_equal expected, m.body
|
||||
end
|
||||
|
||||
def test_nested_attachments_are_recognized_correctly
|
||||
fixture = File.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_nested_attachment")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
assert_equal 2, mail.attachments.length
|
||||
assert_equal "image/png", mail.attachments.first.content_type
|
||||
assert_equal 1902, mail.attachments.first.length
|
||||
assert_equal "application/pkcs7-signature", mail.attachments.last.content_type
|
||||
end
|
||||
end
|
||||
@@ -33,10 +33,10 @@ class ActionMailerUrlTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def new_mail( charset="utf-8" )
|
||||
mail = TMail::Mail.new
|
||||
mail = Mail.new
|
||||
mail.mime_version = "1.0"
|
||||
if charset
|
||||
mail.set_content_type "text", "plain", { "charset" => charset }
|
||||
mail.content_type ["text", "plain", { "charset" => charset }]
|
||||
end
|
||||
mail
|
||||
end
|
||||
@@ -69,10 +69,16 @@ class ActionMailerUrlTest < Test::Unit::TestCase
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.create_signed_up_with_url(@recipient) }
|
||||
assert_not_nil created
|
||||
|
||||
expected.message_id = '<123@456>'
|
||||
created.message_id = '<123@456>'
|
||||
assert_equal expected.encoded, created.encoded
|
||||
|
||||
assert_nothing_raised { TestMailer.deliver_signed_up_with_url(@recipient) }
|
||||
assert_not_nil ActionMailer::Base.deliveries.first
|
||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
||||
delivered = ActionMailer::Base.deliveries.first
|
||||
|
||||
delivered.message_id = '<123@456>'
|
||||
assert_equal expected.encoded, delivered.encoded
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user