Merge remote branch 'mainstream/master'
This commit is contained in:
commit
cda36a0731
8
.gitignore
vendored
8
.gitignore
vendored
@ -7,16 +7,9 @@ activerecord/doc
|
||||
actionpack/doc
|
||||
actionmailer/doc
|
||||
activesupport/doc
|
||||
activemodel/pkg
|
||||
activeresource/pkg
|
||||
activerecord/pkg
|
||||
actionpack/pkg
|
||||
activemodel/test/fixtures/fixture_database.sqlite3
|
||||
actionmailer/pkg
|
||||
activesupport/pkg
|
||||
actionpack/test/tmp
|
||||
activesupport/test/fixtures/isolation_test
|
||||
railties/pkg
|
||||
railties/test/500.html
|
||||
railties/test/fixtures/tmp
|
||||
railties/test/initializer/root/log
|
||||
@ -31,3 +24,4 @@ railties/guides/output
|
||||
bin
|
||||
vendor/gems/
|
||||
railties/tmp
|
||||
pkg
|
||||
|
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -1,6 +1,6 @@
|
||||
[submodule "arel"]
|
||||
path = arel
|
||||
url = git://github.com/rails/arel.git
|
||||
[submodule "rack-mount"]
|
||||
path = rack-mount
|
||||
url = git://github.com/rails/rack-mount.git
|
||||
[submodule "rack"]
|
||||
path = rack
|
||||
url = git://github.com/rails/rack.git
|
||||
|
24
Gemfile
24
Gemfile
@ -1,23 +1,27 @@
|
||||
clear_sources
|
||||
source 'http://gemcutter.org'
|
||||
|
||||
gem "rake", ">= 0.8.7"
|
||||
gem "mocha", ">= 0.9.8"
|
||||
gem "ruby-debug", ">= 0.10.3" if RUBY_VERSION < '1.9'
|
||||
|
||||
gem "rails", "3.0.pre", :vendored_at => "railties"
|
||||
gem "rails", "3.0.pre", :path => "railties"
|
||||
%w(activesupport activemodel actionpack actionmailer activerecord activeresource).each do |lib|
|
||||
gem lib, '3.0.pre', :vendored_at => lib
|
||||
gem lib, '3.0.pre', :path => lib
|
||||
end
|
||||
|
||||
# AS
|
||||
gem "i18n", ">= 0.3.0"
|
||||
|
||||
# AR
|
||||
gem "arel", "0.2.pre", :git => "git://github.com/rails/arel.git"
|
||||
gem "sqlite3-ruby", ">= 1.2.5"
|
||||
gem "pg", ">= 0.8.0"
|
||||
gem "mysql", ">= 2.8.1"
|
||||
|
||||
only :test do
|
||||
gem "pg", ">= 0.8.0"
|
||||
gem "mysql", ">= 2.8.1"
|
||||
end
|
||||
|
||||
# AP
|
||||
gem "rack", "1.0.1", :git => "git://github.com/rails/rack.git"
|
||||
gem "rack-mount", :git => "git://github.com/rails/rack-mount.git"
|
||||
gem "rack", "1.1.0", :git => "git://github.com/rack/rack.git"
|
||||
gem "rack-test", "0.5.3"
|
||||
gem "RedCloth", ">= 4.2.2"
|
||||
|
||||
if ENV['CI']
|
||||
@ -34,3 +38,5 @@ if ENV['CI']
|
||||
gem "test-unit", ">= 2.0.5"
|
||||
end
|
||||
end
|
||||
|
||||
disable_system_gems
|
||||
|
22
Rakefile
22
Rakefile
@ -1,9 +1,10 @@
|
||||
require 'rake'
|
||||
require 'rake/rdoctask'
|
||||
require 'rake/gempackagetask'
|
||||
|
||||
env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD']
|
||||
|
||||
PROJECTS = %w(activesupport actionpack actionmailer activeresource activerecord activemodel railties)
|
||||
PROJECTS = %w(activesupport activemodel actionpack actionmailer activeresource activerecord railties)
|
||||
|
||||
Dir["#{File.dirname(__FILE__)}/*/lib/*/version.rb"].each do |version_path|
|
||||
require version_path
|
||||
@ -23,11 +24,28 @@ task :default => %w(test test:isolated)
|
||||
end
|
||||
end
|
||||
|
||||
desc "Smoke-test all projects"
|
||||
task :smoke do
|
||||
(PROJECTS - %w(activerecord)).each do |project|
|
||||
system %(cd #{project} && #{env} #{$0} test:isolated)
|
||||
end
|
||||
system %(cd activerecord && #{env} #{$0} sqlite3:isolated_test)
|
||||
end
|
||||
|
||||
spec = eval(File.read('rails.gemspec'))
|
||||
Rake::GemPackageTask.new(spec) do |pkg|
|
||||
pkg.gem_spec = spec
|
||||
end
|
||||
|
||||
task :install => :gem do
|
||||
system %(cd arel && gem build arel.gemspec && gem install arel-0.2.pre.gem --no-ri --no-rdoc --ignore-dependencies)
|
||||
system %(cd rack && rake gem VERSION=1.0.2.pre && gem install rack-1.0.2.pre.gem --no-ri --no-rdoc --ignore-dependencies)
|
||||
(PROJECTS - ["railties"]).each do |project|
|
||||
puts "INSTALLING #{project}"
|
||||
system("gem install #{project}/pkg/#{project}-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc")
|
||||
end
|
||||
system("gem install railties/pkg/rails-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc")
|
||||
system("gem install railties/pkg/railties-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc")
|
||||
system("gem install pkg/rails-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc")
|
||||
end
|
||||
|
||||
desc "Generate documentation for the Rails framework"
|
||||
|
@ -1,3 +1,27 @@
|
||||
*Rails 3.0 (pending)*
|
||||
|
||||
* The Mail::Message class has helped methods for all the field types that return 'common' defaults for the common use case, so to get the subject, mail.subject will give you a string, mail.date will give you a DateTime object, mail.from will give you an array of address specs (mikel@test.lindsaar.net) etc. If you want to access the field object itself, call mail[:field_name] which will return the field object you want, which you can then chain, like mail[:from].formatted
|
||||
|
||||
* Mail#content_type now returns the content_type field as a string. If you want the mime type of a mail, then you call Mail#mime_type (eg, text/plain), if you want the parameters of the content type field, you call Mail#content_type_parameters which gives you a hash, eg {'format' => 'flowed', 'charset' => 'utf-8'}
|
||||
|
||||
* 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 object in 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.
|
||||
* 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]
|
||||
|
@ -23,7 +23,6 @@ task :default => [ :test ]
|
||||
Rake::TestTask.new { |t|
|
||||
t.libs << "test"
|
||||
t.pattern = 'test/*_test.rb'
|
||||
t.verbose = true
|
||||
t.warning = true
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
|
||||
s.add_dependency('actionpack', '= 3.0.pre')
|
||||
s.add_dependency('mail', '~> 1.5.0')
|
||||
|
||||
s.files = Dir['CHANGELOG', 'README', 'MIT-LICENSE', 'lib/**/*']
|
||||
s.has_rdoc = true
|
||||
|
@ -21,36 +21,27 @@
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#++
|
||||
|
||||
actionpack_path = "#{File.dirname(__FILE__)}/../../actionpack/lib"
|
||||
$:.unshift(actionpack_path) if File.directory?(actionpack_path)
|
||||
actionpack_path = File.expand_path('../../../actionpack/lib', __FILE__)
|
||||
$:.unshift(actionpack_path) if File.directory?(actionpack_path) && !$:.include?(actionpack_path)
|
||||
|
||||
require 'action_controller'
|
||||
require 'action_view'
|
||||
|
||||
module ActionMailer
|
||||
def self.load_all!
|
||||
[Base, Part, ::Text::Format, ::Net::SMTP]
|
||||
end
|
||||
extend ::ActiveSupport::Autoload
|
||||
|
||||
autoload :AdvAttrAccessor, 'action_mailer/adv_attr_accessor'
|
||||
autoload :DeprecatedBody, 'action_mailer/deprecated_body'
|
||||
autoload :Base, 'action_mailer/base'
|
||||
autoload :DeliveryMethod, 'action_mailer/delivery_method'
|
||||
autoload :Part, 'action_mailer/part'
|
||||
autoload :PartContainer, 'action_mailer/part_container'
|
||||
autoload :Quoting, 'action_mailer/quoting'
|
||||
autoload :TestCase, 'action_mailer/test_case'
|
||||
autoload :TestHelper, 'action_mailer/test_helper'
|
||||
autoload :Utils, 'action_mailer/utils'
|
||||
autoload :AdvAttrAccessor
|
||||
autoload :Base
|
||||
autoload :DeliveryMethod
|
||||
autoload :DeprecatedBody
|
||||
autoload :MailHelper
|
||||
autoload :Quoting
|
||||
autoload :TestCase
|
||||
autoload :TestHelper
|
||||
end
|
||||
|
||||
module Text
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Format, 'action_mailer/vendor/text_format'
|
||||
end
|
||||
|
||||
module Net
|
||||
autoload :SMTP, 'net/smtp'
|
||||
end
|
||||
|
||||
autoload :MailHelper, 'action_mailer/mail_helper'
|
||||
|
||||
require 'action_mailer/vendor/tmail'
|
||||
|
@ -1,29 +1,25 @@
|
||||
module ActionMailer
|
||||
module AdvAttrAccessor #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
def adv_attr_accessor(*names)
|
||||
names.each do |name|
|
||||
ivar = "@#{name}"
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
def adv_attr_accessor(*names)
|
||||
names.each do |name|
|
||||
ivar = "@#{name}"
|
||||
|
||||
define_method("#{name}=") do |value|
|
||||
instance_variable_set(ivar, value)
|
||||
class_eval <<-ACCESSORS, __FILE__, __LINE__ + 1
|
||||
def #{name}=(value)
|
||||
#{ivar} = value
|
||||
end
|
||||
|
||||
define_method(name) do |*parameters|
|
||||
raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1
|
||||
if parameters.empty?
|
||||
if instance_variable_names.include?(ivar)
|
||||
instance_variable_get(ivar)
|
||||
end
|
||||
def #{name}(*args)
|
||||
raise ArgumentError, "expected 0 or 1 parameters" unless args.length <= 1
|
||||
if args.empty?
|
||||
#{ivar} if instance_variable_names.include?(#{ivar.inspect})
|
||||
else
|
||||
instance_variable_set(ivar, parameters.first)
|
||||
#{ivar} = args.first
|
||||
end
|
||||
end
|
||||
end
|
||||
ACCESSORS
|
||||
|
||||
self.protected_instance_variables << ivar if self.respond_to?(:protected_instance_variables)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,9 +1,10 @@
|
||||
require 'active_support/core_ext/class'
|
||||
require 'mail'
|
||||
require 'action_mailer/tmail_compat'
|
||||
|
||||
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.
|
||||
@ -22,7 +23,8 @@ module ActionMailer #:nodoc:
|
||||
# bcc ["bcc@example.com", "Order Watcher <watcher@example.com>"]
|
||||
# from "system@example.com"
|
||||
# subject "New account information"
|
||||
# body :account => recipient
|
||||
#
|
||||
# @account = recipient
|
||||
# end
|
||||
# end
|
||||
#
|
||||
@ -42,13 +44,6 @@ module ActionMailer #:nodoc:
|
||||
# address. Setting this is useful when you want delivery notifications sent to a different address than
|
||||
# the one in <tt>from</tt>.
|
||||
#
|
||||
# The <tt>body</tt> method has special behavior. It takes a hash which generates an instance variable
|
||||
# named after each key in the hash containing the value that that key points to.
|
||||
#
|
||||
# So, for example, <tt>body :account => recipient</tt> would result
|
||||
# in an instance variable <tt>@account</tt> with the value of <tt>recipient</tt> being accessible in the
|
||||
# view.
|
||||
#
|
||||
#
|
||||
# = Mailer views
|
||||
#
|
||||
@ -68,7 +63,12 @@ module ActionMailer #:nodoc:
|
||||
# You can even use Action Pack helpers in these views. For example:
|
||||
#
|
||||
# You got a new note!
|
||||
# <%= truncate(note.body, 25) %>
|
||||
# <%= truncate(@note.body, 25) %>
|
||||
#
|
||||
# If you need to access the subject, from or the recipients in the view, you can do that through mailer object:
|
||||
#
|
||||
# You got a new note from <%= mailer.from %>!
|
||||
# <%= truncate(@note.body, 25) %>
|
||||
#
|
||||
#
|
||||
# = Generating URLs
|
||||
@ -150,7 +150,7 @@ module ActionMailer #:nodoc:
|
||||
#
|
||||
# part "text/plain" do |p|
|
||||
# p.body = render_message("signup-as-plain", :account => recipient)
|
||||
# p.transfer_encoding = "base64"
|
||||
# p.content_transfer_encoding = "base64"
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
@ -250,29 +250,23 @@ module ActionMailer #:nodoc:
|
||||
# <tt>["text/html", "text/enriched", "text/plain"]</tt>. Items that appear first in the array have higher priority in the mail client
|
||||
# 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
|
||||
include AdvAttrAccessor, PartContainer, Quoting, Utils
|
||||
class Base < AbstractController::Base
|
||||
include Quoting
|
||||
extend AdvAttrAccessor
|
||||
|
||||
include AbstractController::RenderingController
|
||||
include AbstractController::Logger
|
||||
include AbstractController::Rendering
|
||||
include AbstractController::LocalizedCache
|
||||
include AbstractController::Layouts
|
||||
|
||||
include AbstractController::Helpers
|
||||
helper MailHelper
|
||||
|
||||
if Object.const_defined?(:ActionController)
|
||||
include ActionController::UrlWriter
|
||||
end
|
||||
helper ActionMailer::MailHelper
|
||||
|
||||
include ActionController::UrlWriter
|
||||
include ActionMailer::DeprecatedBody
|
||||
|
||||
private_class_method :new #:nodoc:
|
||||
|
||||
class_inheritable_accessor :view_paths
|
||||
self.view_paths = []
|
||||
|
||||
cattr_accessor :logger
|
||||
|
||||
@@raise_delivery_errors = true
|
||||
cattr_accessor :raise_delivery_errors
|
||||
|
||||
@ -291,10 +285,16 @@ class Base
|
||||
@@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 = []
|
||||
@@protected_instance_variables = %w(@parts @mail)
|
||||
cattr_reader :protected_instance_variables
|
||||
|
||||
# Specify the BCC addresses for the message
|
||||
@ -344,24 +344,13 @@ class Base
|
||||
# have multiple mailer methods share the same template.
|
||||
adv_attr_accessor :template
|
||||
|
||||
# The mail and action_name instances referenced by this mailer.
|
||||
attr_reader :mail, :action_name
|
||||
|
||||
# Where the response body is stored.
|
||||
attr_internal :response_body
|
||||
|
||||
# Override the mailer name, which defaults to an inflected version of the
|
||||
# mailer's class name. If you want to use a template in a non-standard
|
||||
# location, you can use this to specify that location.
|
||||
attr_writer :mailer_name
|
||||
adv_attr_accessor :mailer_name
|
||||
|
||||
def mailer_name(value = nil)
|
||||
if value
|
||||
@mailer_name = value
|
||||
else
|
||||
@mailer_name || self.class.mailer_name
|
||||
end
|
||||
end
|
||||
# Expose the internal mail
|
||||
attr_reader :mail
|
||||
|
||||
# Alias controller_path to mailer_name so render :partial in views work.
|
||||
alias :controller_path :mailer_name
|
||||
@ -376,6 +365,7 @@ class << self
|
||||
def mailer_name
|
||||
@mailer_name ||= name.underscore
|
||||
end
|
||||
alias :controller_path :mailer_name
|
||||
|
||||
def delivery_method=(method_name)
|
||||
@delivery_method = ActionMailer::DeliveryMethod.lookup_method(method_name)
|
||||
@ -411,8 +401,7 @@ def method_missing(method_symbol, *parameters) #: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
|
||||
|
||||
@ -447,21 +436,47 @@ def matches_dynamic_method?(method_name) #: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, *parameters) #:nodoc:
|
||||
@_response_body = nil
|
||||
def initialize(method_name=nil, *args)
|
||||
super()
|
||||
create!(method_name, *parameters) if method_name
|
||||
process(method_name, *args) if method_name
|
||||
end
|
||||
|
||||
# Initialize the mailer via the given +method_name+. The body will be
|
||||
# rendered and a new TMail::Mail object created.
|
||||
def create!(method_name, *parameters) #:nodoc:
|
||||
# Process the mailer via the given +method_name+. The body will be
|
||||
# rendered and a new Mail object created.
|
||||
def process(method_name, *args)
|
||||
initialize_defaults(method_name)
|
||||
__send__(method_name, *parameters)
|
||||
super
|
||||
|
||||
# Create e-mail parts
|
||||
create_parts
|
||||
@ -470,11 +485,11 @@ def create!(method_name, *parameters) #:nodoc:
|
||||
@subject ||= I18n.t(:subject, :scope => [:actionmailer, mailer_name, method_name],
|
||||
:default => method_name.humanize)
|
||||
|
||||
# build the mail object itself
|
||||
@mail = create_mail
|
||||
# Build the mail object itself
|
||||
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)
|
||||
@ -485,7 +500,7 @@ def deliver!(mail = @mail)
|
||||
logger.debug "\n#{mail.encoded}"
|
||||
end
|
||||
|
||||
ActiveSupport::Notifications.instrument(:deliver_mail, :mail => @mail) do
|
||||
ActiveSupport::Notifications.instrument(:deliver_mail, :mail => mail) do
|
||||
begin
|
||||
self.delivery_method.perform_delivery(mail) if perform_deliveries
|
||||
rescue Exception => e # Net::SMTP errors or sendmail pipe errors
|
||||
@ -501,15 +516,14 @@ def deliver!(mail = @mail)
|
||||
# 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
|
||||
@mime_version ||= @@default_mime_version.dup if @@default_mime_version
|
||||
|
||||
@mailer_name ||= self.class.mailer_name
|
||||
@mailer_name ||= self.class.mailer_name.dup
|
||||
@template ||= method_name
|
||||
@action_name = @template
|
||||
|
||||
@parts ||= []
|
||||
@headers ||= {}
|
||||
@ -518,29 +532,18 @@ def initialize_defaults(method_name)
|
||||
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
|
||||
@ -549,37 +552,19 @@ def create_parts
|
||||
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)
|
||||
@ -592,27 +577,43 @@ def create_mail
|
||||
headers.each { |k, v| m[k] = v }
|
||||
|
||||
real_content_type, ctype_attrs = parse_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)
|
||||
main_type, sub_type = split_content_type(real_content_type)
|
||||
|
||||
if @parts.size == 1 && @parts.first.parts.empty?
|
||||
m.content_type([main_type, sub_type, ctype_attrs])
|
||||
m.body = @parts.first.body.encoded
|
||||
else
|
||||
@parts.each do |p|
|
||||
part = (TMail::Mail === p ? p : p.to_mail(self))
|
||||
m.parts << part
|
||||
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.set_content_type(real_content_type, nil, ctype_attrs)
|
||||
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
|
||||
|
@ -1,7 +1,7 @@
|
||||
require "active_support/core_ext/class"
|
||||
require 'active_support/core_ext/class'
|
||||
|
||||
module ActionMailer
|
||||
module DeliveryMethod
|
||||
|
||||
autoload :File, 'action_mailer/delivery_method/file'
|
||||
autoload :Sendmail, 'action_mailer/delivery_method/sendmail'
|
||||
autoload :Smtp, 'action_mailer/delivery_method/smtp'
|
||||
@ -52,6 +52,5 @@ class Method
|
||||
superclass_delegating_accessor :settings
|
||||
self.settings = {}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -6,13 +6,13 @@ module DeliveryMethod
|
||||
# A delivery method implementation which writes all mails to a file.
|
||||
class File < Method
|
||||
self.settings = {
|
||||
:location => defined?(Rails) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails"
|
||||
:location => defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails"
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -1,8 +1,9 @@
|
||||
require 'net/smtp'
|
||||
|
||||
module ActionMailer
|
||||
module DeliveryMethod
|
||||
# A delivery method implementation which sends via smtp.
|
||||
class Smtp < Method
|
||||
|
||||
self.settings = {
|
||||
:address => "localhost",
|
||||
:port => 25,
|
||||
@ -15,8 +16,7 @@ class Smtp < Method
|
||||
|
||||
def perform_delivery(mail)
|
||||
destinations = mail.destinations
|
||||
mail.ready_to_send
|
||||
sender = (mail['return-path'] && mail['return-path'].spec) || mail['from']
|
||||
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)
|
||||
@ -26,6 +26,5 @@ def perform_delivery(mail)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -15,6 +15,14 @@ def initialize_defaults(method_name)
|
||||
@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 ' <<
|
||||
|
@ -1,17 +1,24 @@
|
||||
module MailHelper
|
||||
# Uses Text::Format to take the text and format it, indented two spaces for
|
||||
# each line, and wrapped at 72 columns.
|
||||
def block_format(text)
|
||||
formatted = text.split(/\n\r\n/).collect { |paragraph|
|
||||
Text::Format.new(
|
||||
:columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph
|
||||
).format
|
||||
}.join("\n")
|
||||
module ActionMailer
|
||||
module MailHelper
|
||||
# Uses Text::Format to take the text and format it, indented two spaces for
|
||||
# each line, and wrapped at 72 columns.
|
||||
def block_format(text)
|
||||
formatted = text.split(/\n\r\n/).collect { |paragraph|
|
||||
Text::Format.new(
|
||||
:columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph
|
||||
).format
|
||||
}.join("\n")
|
||||
|
||||
# Make list points stand on their own line
|
||||
formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
||||
formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
||||
# Make list points stand on their own line
|
||||
formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
||||
formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
||||
|
||||
formatted
|
||||
end
|
||||
|
||||
formatted
|
||||
# Access the mailer instance.
|
||||
def mailer #:nodoc:
|
||||
@controller
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,107 +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 AdvAttrAccessor, PartContainer, Utils
|
||||
|
||||
# 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
|
||||
|
||||
@parts.each do |p|
|
||||
prt = (TMail::Mail === p ? p : p.to_mail(defaults))
|
||||
part.parts << prt
|
||||
end
|
||||
|
||||
if real_content_type =~ /multipart/
|
||||
ctype_attrs.delete 'charset'
|
||||
part.set_content_type(real_content_type, nil, ctype_attrs)
|
||||
end
|
||||
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,55 +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 parse_content_type(defaults=nil)
|
||||
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 @@ def quote_any_if_necessary(charset, *args)
|
||||
# "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)
|
||||
|
25
actionmailer/lib/action_mailer/railtie.rb
Normal file
25
actionmailer/lib/action_mailer/railtie.rb
Normal file
@ -0,0 +1,25 @@
|
||||
require "action_mailer"
|
||||
require "rails"
|
||||
|
||||
module ActionMailer
|
||||
class Railtie < Rails::Railtie
|
||||
plugin_name :action_mailer
|
||||
|
||||
initializer "action_mailer.set_configs" do |app|
|
||||
app.config.action_mailer.each do |k,v|
|
||||
ActionMailer::Base.send "#{k}=", v
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: ActionController::Base.logger should delegate to its own config.logger
|
||||
initializer "action_mailer.logger" do
|
||||
ActionMailer::Base.logger ||= Rails.logger
|
||||
end
|
||||
|
||||
initializer "action_mailer.view_paths" do |app|
|
||||
# TODO: this should be combined with the logic for default config.action_mailer.view_paths
|
||||
view_path = ActionView::PathSet.type_cast(app.config.view_path, app.config.cache_classes)
|
||||
ActionMailer::Base.template_root = view_path if ActionMailer::Base.view_paths.blank?
|
||||
end
|
||||
end
|
||||
end
|
@ -1,4 +1,5 @@
|
||||
require 'active_support/test_case'
|
||||
require 'action_mailer/base'
|
||||
|
||||
module ActionMailer
|
||||
class NonInferrableMailerError < ::StandardError
|
||||
@ -43,8 +44,8 @@ def initialize_test_deliveries
|
||||
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
|
||||
|
||||
|
20
actionmailer/lib/action_mailer/tmail_compat.rb
Normal file
20
actionmailer/lib/action_mailer/tmail_compat.rb
Normal file
@ -0,0 +1,20 @@
|
||||
module Mail
|
||||
class Message
|
||||
|
||||
def set_content_type(*args)
|
||||
STDERR.puts("Message#set_content_type is deprecated, please just call Message#content_type with the same arguments.\n#{caller}")
|
||||
content_type(*args)
|
||||
end
|
||||
|
||||
alias :old_transfer_encoding :transfer_encoding
|
||||
def transfer_encoding(value = nil)
|
||||
if value
|
||||
STDERR.puts("Message#transfer_encoding is deprecated, please call Message#content_transfer_encoding with the same arguments.\n#{caller}")
|
||||
content_transfer_encoding(value)
|
||||
else
|
||||
old_transfer_encoding
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -1,7 +0,0 @@
|
||||
module ActionMailer
|
||||
module Utils #:nodoc:
|
||||
def normalize_new_lines(text)
|
||||
text.to_s.gsub(/\r\n?/, "\n")
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -1,12 +1,9 @@
|
||||
root = File.expand_path('../../..', __FILE__)
|
||||
begin
|
||||
require "#{root}/vendor/gems/environment"
|
||||
require File.expand_path('../../../vendor/gems/environment', __FILE__)
|
||||
rescue LoadError
|
||||
$:.unshift("#{root}/activesupport/lib")
|
||||
$:.unshift("#{root}/actionpack/lib")
|
||||
end
|
||||
|
||||
lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
|
||||
lib = File.expand_path('../../lib', __FILE__)
|
||||
$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
|
||||
|
||||
require 'rubygems'
|
||||
|
@ -1,18 +1,36 @@
|
||||
require 'abstract_unit'
|
||||
require 'action_mailer/adv_attr_accessor'
|
||||
|
||||
class AdvAttrTest < Test::Unit::TestCase
|
||||
class AdvAttrTest < ActiveSupport::TestCase
|
||||
class Person
|
||||
include ActionMailer::AdvAttrAccessor
|
||||
cattr_reader :protected_instance_variables
|
||||
@@protected_instance_variables = []
|
||||
|
||||
extend ActionMailer::AdvAttrAccessor
|
||||
adv_attr_accessor :name
|
||||
end
|
||||
|
||||
def test_adv_attr
|
||||
bob = Person.new
|
||||
assert_nil bob.name
|
||||
bob.name 'Bob'
|
||||
assert_equal 'Bob', bob.name
|
||||
def setup
|
||||
@person = Person.new
|
||||
end
|
||||
|
||||
assert_raise(ArgumentError) {bob.name 'x', 'y'}
|
||||
def test_adv_attr
|
||||
assert_nil @person.name
|
||||
@person.name 'Bob'
|
||||
assert_equal 'Bob', @person.name
|
||||
end
|
||||
|
||||
def test_adv_attr_writer
|
||||
assert_nil @person.name
|
||||
@person.name = 'Bob'
|
||||
assert_equal 'Bob', @person.name
|
||||
end
|
||||
|
||||
def test_raise_an_error_with_multiple_args
|
||||
assert_raise(ArgumentError) { @person.name('x', 'y') }
|
||||
end
|
||||
|
||||
def test_ivar_is_added_to_protected_instnace_variables
|
||||
assert Person.protected_instance_variables.include?('@name')
|
||||
end
|
||||
end
|
||||
|
@ -24,7 +24,7 @@ def teardown
|
||||
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 @@ def test_asset_host_as_one_arguement_proc
|
||||
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 @@ def test_asset_host_as_two_arguement_proc
|
||||
}
|
||||
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 @@ def name_of_the_mailer_class
|
||||
|
||||
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 @@ def test_use_helper_method
|
||||
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 @@ def teardown
|
||||
|
||||
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.mime_type
|
||||
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.mime_type
|
||||
|
||||
# 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.mime_type
|
||||
|
||||
# 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.mime_type
|
||||
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.mime_type
|
||||
# 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.mime_type
|
||||
# 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.mime_type
|
||||
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.mime_type
|
||||
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.mime_type
|
||||
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
|
||||
|
@ -40,13 +40,22 @@ def included_subtemplate(recipient)
|
||||
from "tester@example.com"
|
||||
end
|
||||
|
||||
def included_old_subtemplate(recipient)
|
||||
def mailer_accessor(recipient)
|
||||
recipients recipient
|
||||
subject "Including another template in the one being rendered"
|
||||
subject "Mailer Accessor"
|
||||
from "tester@example.com"
|
||||
|
||||
@world = "Earth"
|
||||
render :inline => "Hello, <%= render \"subtemplate\" %>"
|
||||
render :inline => "Look, <%= mailer.subject %>!"
|
||||
end
|
||||
|
||||
def no_instance_variable(recipient)
|
||||
recipients recipient
|
||||
subject "No Instance Variable"
|
||||
from "tester@example.com"
|
||||
|
||||
silence_warnings do
|
||||
render :inline => "Look, subject.nil? is <%= @subject.nil? %>!"
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_defaults(method_name)
|
||||
@ -71,6 +80,8 @@ def share(recipient)
|
||||
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
|
||||
@ -86,27 +97,37 @@ def teardown
|
||||
|
||||
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.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.to_s.strip
|
||||
end
|
||||
end
|
||||
|
||||
@ -125,12 +146,12 @@ def teardown
|
||||
|
||||
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 @@ def multipart_with_template_path_with_dots(recipient)
|
||||
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
|
||||
|
||||
@ -31,6 +31,18 @@ def cancelled_account(recipient)
|
||||
render :text => "Goodbye, Mr. #{recipient}"
|
||||
end
|
||||
|
||||
def from_with_name
|
||||
from "System <system@loudthinking.com>"
|
||||
recipients "root@loudthinking.com"
|
||||
render :text => "Nothing to see here."
|
||||
end
|
||||
|
||||
def from_without_name
|
||||
from "system@loudthinking.com"
|
||||
recipients "root@loudthinking.com"
|
||||
render :text => "Nothing to see here."
|
||||
end
|
||||
|
||||
def cc_bcc(recipient)
|
||||
recipients recipient
|
||||
subject "testing bcc/cc"
|
||||
@ -95,7 +107,7 @@ def utf8_body(recipient)
|
||||
cc "Foo áëô îü <extended@example.net>"
|
||||
bcc "Foo áëô îü <extended@example.net>"
|
||||
charset "utf-8"
|
||||
|
||||
|
||||
render :text => "åœö blah"
|
||||
end
|
||||
|
||||
@ -143,8 +155,8 @@ def explicitly_multipart_example(recipient, ct=nil)
|
||||
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
|
||||
@ -227,12 +239,12 @@ def nested_multipart(recipient)
|
||||
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)
|
||||
@ -251,8 +263,8 @@ def attachment_with_custom_header(recipient)
|
||||
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)
|
||||
@ -261,7 +273,7 @@ def unnamed_attachment(recipient)
|
||||
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)
|
||||
@ -289,6 +301,7 @@ def return_path
|
||||
render :text => "testing"
|
||||
end
|
||||
|
||||
# This tests body calls accepeting a hash, which is deprecated.
|
||||
def body_ivar(recipient)
|
||||
recipients recipient
|
||||
subject "Body as a local variable"
|
||||
@ -319,10 +332,10 @@ def encode( text, charset="utf-8" )
|
||||
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
|
||||
@ -346,30 +359,33 @@ def teardown
|
||||
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.mime_type
|
||||
assert_equal "multipart/alternative", created.parts[0].mime_type
|
||||
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].mime_type
|
||||
assert_equal "text/html", created.parts[0].parts[1].mime_type
|
||||
assert_equal "application/octet-stream", created.parts[1].mime_type
|
||||
|
||||
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.mime_type
|
||||
assert_equal "multipart/alternative", created.parts.first.mime_type
|
||||
assert_equal "text/plain", created.parts.first.parts.first.mime_type
|
||||
assert_equal "Nothing to see here.", created.parts.first.parts.first.body.to_s
|
||||
assert_equal "text/html", created.parts.first.parts.second.mime_type
|
||||
assert_equal "<b>test</b> HTML<br/>", created.parts.first.parts.second.body.to_s
|
||||
end
|
||||
|
||||
def test_attachment_with_custom_header
|
||||
@ -391,20 +407,30 @@ def test_signed_up
|
||||
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
|
||||
@ -418,6 +444,8 @@ def test_custom_template
|
||||
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
|
||||
|
||||
@ -440,8 +468,8 @@ def test_custom_templating_extension
|
||||
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].mime_type
|
||||
assert_equal 'text/html', created.parts[1].mime_type
|
||||
end
|
||||
|
||||
def test_cancelled_account
|
||||
@ -455,11 +483,17 @@ def test_cancelled_account
|
||||
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
|
||||
@ -477,6 +511,8 @@ def test_cc_bcc
|
||||
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
|
||||
@ -484,7 +520,33 @@ def test_cc_bcc
|
||||
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
|
||||
ActionMailer::Base.delivery_method = :smtp
|
||||
TestMailer.deliver_from_without_name
|
||||
|
||||
mail = MockSMTP.deliveries.first
|
||||
assert_not_nil mail
|
||||
mail, from, to = mail
|
||||
|
||||
assert_equal 'system@loudthinking.com', from.to_s
|
||||
end
|
||||
|
||||
def test_from_with_name_for_smtp
|
||||
ActionMailer::Base.delivery_method = :smtp
|
||||
TestMailer.deliver_from_with_name
|
||||
|
||||
mail = MockSMTP.deliveries.first
|
||||
assert_not_nil mail
|
||||
mail, from, to = mail
|
||||
|
||||
assert_equal 'system@loudthinking.com', from.addresses.first
|
||||
end
|
||||
|
||||
def test_reply_to
|
||||
@ -502,14 +564,23 @@ def test_reply_to
|
||||
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
|
||||
@ -527,14 +598,23 @@ def test_iso_charset
|
||||
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
|
||||
@ -552,14 +632,23 @@ def test_unencoded_subject
|
||||
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
|
||||
@ -625,7 +714,12 @@ def test_delivery_logs_sent_mail
|
||||
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
|
||||
@ -636,9 +730,9 @@ def test_unquote_quoted_printable_subject
|
||||
|
||||
The body
|
||||
EOF
|
||||
mail = TMail::Mail.parse(msg)
|
||||
mail = Mail.new(msg)
|
||||
assert_equal "testing testing \326\244", mail.subject
|
||||
assert_equal "=?utf-8?Q?testing_testing_=D6=A4?=", mail.quoted_subject
|
||||
assert_equal "Subject: =?utf-8?Q?testing_testing_=D6=A4?=\r\n", mail[:subject].encoded
|
||||
end
|
||||
|
||||
def test_unquote_7bit_subject
|
||||
@ -649,9 +743,9 @@ def test_unquote_7bit_subject
|
||||
|
||||
The body
|
||||
EOF
|
||||
mail = TMail::Mail.parse(msg)
|
||||
mail = Mail.new(msg)
|
||||
assert_equal "this == working?", mail.subject
|
||||
assert_equal "this == working?", mail.quoted_subject
|
||||
assert_equal "Subject: this == working?\r\n", mail[:subject].encoded
|
||||
end
|
||||
|
||||
def test_unquote_7bit_body
|
||||
@ -663,9 +757,9 @@ def test_unquote_7bit_body
|
||||
|
||||
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
|
||||
@ -677,9 +771,9 @@ def test_unquote_quoted_printable_body
|
||||
|
||||
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
|
||||
@ -691,9 +785,9 @@ def test_unquote_base64_body
|
||||
|
||||
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
|
||||
@ -714,14 +808,22 @@ def test_extended_headers
|
||||
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
|
||||
@ -752,40 +854,40 @@ def test_multiple_utf8_recipients
|
||||
|
||||
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.mime_type
|
||||
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
|
||||
@ -793,19 +895,19 @@ def test_attachment_with_text_type
|
||||
|
||||
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
|
||||
|
||||
@ -827,30 +929,29 @@ def test_implicitly_multipart_with_utf8
|
||||
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.mime_type
|
||||
assert_equal "text/plain", mail.parts[0].mime_type
|
||||
|
||||
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].mime_type
|
||||
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].mime_type
|
||||
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.mime_type
|
||||
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.mime_type
|
||||
end
|
||||
|
||||
def test_implicitly_multipart_messages
|
||||
@ -858,14 +959,14 @@ def test_implicitly_multipart_messages
|
||||
|
||||
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.mime_type
|
||||
assert_equal "text/plain", mail.parts[0].mime_type
|
||||
assert_equal "utf-8", mail.parts[0].charset
|
||||
assert_equal "text/html", mail.parts[1].mime_type
|
||||
assert_equal "utf-8", mail.parts[1].charset
|
||||
assert_equal "application/x-yaml", mail.parts[2].mime_type
|
||||
assert_equal "utf-8", mail.parts[2].charset
|
||||
end
|
||||
|
||||
def test_implicitly_multipart_messages_with_custom_order
|
||||
@ -873,41 +974,43 @@ def test_implicitly_multipart_messages_with_custom_order
|
||||
|
||||
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].mime_type
|
||||
assert_equal "text/plain", mail.parts[1].mime_type
|
||||
assert_equal "text/html", mail.parts[2].mime_type
|
||||
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.mime_type
|
||||
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
|
||||
@ -935,35 +1038,41 @@ def test_file_delivery_should_create_a_file
|
||||
|
||||
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
|
||||
|
||||
@ -972,9 +1081,9 @@ def test_headers_with_nonalpha_chars
|
||||
assert !mail.from_addrs.empty?
|
||||
assert !mail.cc_addrs.empty?
|
||||
assert !mail.bcc_addrs.empty?
|
||||
assert_match(/:/, mail.from_addrs.to_s)
|
||||
assert_match(/:/, mail.cc_addrs.to_s)
|
||||
assert_match(/:/, mail.bcc_addrs.to_s)
|
||||
assert_match(/:/, mail[:from].decoded)
|
||||
assert_match(/:/, mail[:cc].decoded)
|
||||
assert_match(/:/, mail[:bcc].decoded)
|
||||
end
|
||||
|
||||
def test_deliver_with_mail_object
|
||||
@ -986,31 +1095,32 @@ def test_deliver_with_mail_object
|
||||
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].mime_type
|
||||
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
|
||||
assert_match %r{charset=utf-8}, mail.content_type
|
||||
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 = TestMailer.create_body_ivar(@recipient)
|
||||
assert_equal "body: foo\nbar: baz", mail.body
|
||||
mail = nil
|
||||
ActiveSupport::Deprecation.silence { mail = TestMailer.create_body_ivar(@recipient) }
|
||||
assert_equal "body: foo\nbar: baz", mail.body.to_s
|
||||
end
|
||||
|
||||
def test_starttls_is_enabled_if_supported
|
||||
@ -1139,6 +1249,6 @@ def test_should_still_raise_exception_with_expected_message_when_calling_an_unde
|
||||
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 @@ def test_quote_multibyte_chars
|
||||
$:.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,12 +68,16 @@ def test_quote_multibyte_chars
|
||||
# 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"))
|
||||
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
|
||||
end
|
||||
|
||||
|
@ -18,9 +18,9 @@ def test_setup_sets_right_action_mailer_options
|
||||
end
|
||||
|
||||
def test_setup_creates_the_expected_mailer
|
||||
assert @expected.is_a?(TMail::Mail)
|
||||
assert @expected.is_a?(Mail::Message)
|
||||
assert_equal "1.0", @expected.mime_version
|
||||
assert_equal "text/plain", @expected.content_type
|
||||
assert_equal "text/plain", @expected.mime_type
|
||||
end
|
||||
|
||||
def test_mailer_class_is_correctly_inferred
|
||||
@ -92,7 +92,7 @@ def test_assert_emails_too_few_sent
|
||||
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 @@ def test_assert_emails_too_many_sent
|
||||
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 @@ def test_assert_no_emails_failure
|
||||
end
|
||||
end
|
||||
|
||||
assert_match /0 .* but 1/, error.message
|
||||
assert_match(/0 .* but 1/, error.message)
|
||||
end
|
||||
end
|
||||
|
||||
@ -125,7 +125,7 @@ def setup
|
||||
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
|
||||
|
23
actionmailer/test/tmail_compat_test.rb
Normal file
23
actionmailer/test/tmail_compat_test.rb
Normal file
@ -0,0 +1,23 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class TmailCompatTest < Test::Unit::TestCase
|
||||
|
||||
def test_set_content_type_raises_deprecation_warning
|
||||
mail = Mail.new
|
||||
STDERR.expects(:puts) # Deprecation warning
|
||||
assert_nothing_raised do
|
||||
mail.set_content_type "text/plain"
|
||||
end
|
||||
assert_equal mail.mime_type, "text/plain"
|
||||
end
|
||||
|
||||
def test_transfer_encoding_raises_deprecation_warning
|
||||
mail = Mail.new
|
||||
STDERR.expects(:puts) # Deprecation warning
|
||||
assert_nothing_raised do
|
||||
mail.transfer_encoding "base64"
|
||||
end
|
||||
assert_equal mail.content_transfer_encoding, "base64"
|
||||
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
|
@ -1,7 +1,9 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class TestMailer < ActionMailer::Base
|
||||
class WelcomeController < ActionController::Base
|
||||
end
|
||||
|
||||
class TestMailer < ActionMailer::Base
|
||||
default_url_options[:host] = 'www.basecamphq.com'
|
||||
|
||||
def signed_up_with_url(recipient)
|
||||
@ -10,8 +12,8 @@ def signed_up_with_url(recipient)
|
||||
@from = "system@loudthinking.com"
|
||||
@sent_on = Time.local(2004, 12, 12)
|
||||
|
||||
@body["recipient"] = recipient
|
||||
@body["welcome_url"] = url_for :host => "example.com", :controller => "welcome", :action => "greeting"
|
||||
@recipient = recipient
|
||||
@welcome_url = url_for :host => "example.com", :controller => "welcome", :action => "greeting"
|
||||
end
|
||||
|
||||
class <<self
|
||||
@ -31,10 +33,10 @@ def encode( text, charset="utf-8" )
|
||||
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
|
||||
@ -52,27 +54,31 @@ def teardown
|
||||
end
|
||||
|
||||
def test_signed_up_with_url
|
||||
ActionController::Routing.use_controllers! ['welcome'] do
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.connect ':controller/:action/:id'
|
||||
map.welcome 'welcome', :controller=>"foo", :action=>"bar"
|
||||
end
|
||||
|
||||
expected = new_mail
|
||||
expected.to = @recipient
|
||||
expected.subject = "[Signed up] Welcome #{@recipient}"
|
||||
expected.body = "Hello there, \n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n<img alt=\"Somelogo\" src=\"/images/somelogo.png\" />"
|
||||
expected.from = "system@loudthinking.com"
|
||||
expected.date = Time.local(2004, 12, 12)
|
||||
|
||||
created = nil
|
||||
assert_nothing_raised { created = TestMailer.create_signed_up_with_url(@recipient) }
|
||||
assert_not_nil created
|
||||
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
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.connect ':controller/:action/:id'
|
||||
map.welcome 'welcome', :controller=>"foo", :action=>"bar"
|
||||
end
|
||||
|
||||
expected = new_mail
|
||||
expected.to = @recipient
|
||||
expected.subject = "[Signed up] Welcome #{@recipient}"
|
||||
expected.body = "Hello there, \n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n<img alt=\"Somelogo\" src=\"/images/somelogo.png\" />"
|
||||
expected.from = "system@loudthinking.com"
|
||||
expected.date = Time.local(2004, 12, 12)
|
||||
|
||||
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
|
||||
delivered = ActionMailer::Base.deliveries.first
|
||||
|
||||
delivered.message_id = '<123@456>'
|
||||
assert_equal expected.encoded, delivered.encoded
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,19 @@
|
||||
*Edge*
|
||||
|
||||
* Added ActionDispatch::Request#authorization to access the http authentication header regardless of its proxy hiding [DHH]
|
||||
|
||||
* Added :alert, :notice, and :flash as options to ActionController::Base#redirect_to that'll automatically set the proper flash before the redirection [DHH]. Examples:
|
||||
|
||||
flash[:notice] = 'Post was created'
|
||||
redirect_to(@post)
|
||||
|
||||
...becomes:
|
||||
|
||||
redirect_to(@post, :notice => 'Post was created')
|
||||
|
||||
* Added ActionController::Base#notice/= and ActionController::Base#alert/= as a convenience accessors in both the controller and the view for flash[:notice]/= and flash[:alert]/= [DHH]
|
||||
|
||||
|
||||
* Introduce grouped_collection_select helper. #1249 [Dan Codeape, Erik Ostrom]
|
||||
|
||||
* Make sure javascript_include_tag/stylesheet_link_tag does not append ".js" or ".css" onto external urls. #1664 [Matthew Rudy Jacobs]
|
||||
|
@ -31,7 +31,6 @@ Rake::TestTask.new(:test_action_pack) do |t|
|
||||
# this will not happen automatically and the tests (as a whole) will error
|
||||
t.test_files = Dir.glob('test/{abstract,controller,dispatch,template}/**/*_test.rb').sort
|
||||
|
||||
t.verbose = true
|
||||
# t.warning = true
|
||||
end
|
||||
|
||||
@ -45,7 +44,6 @@ desc 'ActiveRecord Integration Tests'
|
||||
Rake::TestTask.new(:test_active_record_integration) do |t|
|
||||
t.libs << 'test'
|
||||
t.test_files = Dir.glob("test/activerecord/*_test.rb")
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
# Genereate the RDoc documentation
|
||||
|
@ -16,9 +16,9 @@
|
||||
|
||||
s.add_dependency('activesupport', '= 3.0.pre')
|
||||
s.add_dependency('activemodel', '= 3.0.pre')
|
||||
s.add_dependency('rack', '~> 1.0.1')
|
||||
s.add_dependency('rack', '~> 1.1.0')
|
||||
s.add_dependency('rack-test', '~> 0.5.0')
|
||||
s.add_dependency('rack-mount', '~> 0.0.1')
|
||||
s.add_dependency('rack-mount', '~> 0.4.0')
|
||||
s.add_dependency('erubis', '~> 2.6.5')
|
||||
|
||||
s.require_path = 'lib'
|
||||
|
@ -1,16 +1,18 @@
|
||||
require "active_support/core_ext/module/attr_internal"
|
||||
require "active_support/core_ext/module/delegation"
|
||||
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
|
||||
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
|
||||
|
||||
require 'active_support/ruby/shim'
|
||||
require 'active_support/core_ext/module/attr_internal'
|
||||
require 'active_support/core_ext/module/delegation'
|
||||
|
||||
module AbstractController
|
||||
autoload :Base, "abstract_controller/base"
|
||||
autoload :Callbacks, "abstract_controller/callbacks"
|
||||
autoload :Helpers, "abstract_controller/helpers"
|
||||
autoload :Layouts, "abstract_controller/layouts"
|
||||
autoload :LocalizedCache, "abstract_controller/localized_cache"
|
||||
autoload :Logger, "abstract_controller/logger"
|
||||
autoload :RenderingController, "abstract_controller/rendering_controller"
|
||||
# === Exceptions
|
||||
autoload :ActionNotFound, "abstract_controller/exceptions"
|
||||
autoload :DoubleRenderError, "abstract_controller/exceptions"
|
||||
autoload :Error, "abstract_controller/exceptions"
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Base
|
||||
autoload :Callbacks
|
||||
autoload :Helpers
|
||||
autoload :Layouts
|
||||
autoload :LocalizedCache
|
||||
autoload :Logger
|
||||
autoload :Rendering
|
||||
end
|
||||
|
@ -1,8 +1,11 @@
|
||||
module AbstractController
|
||||
class Error < StandardError; end
|
||||
class ActionNotFound < StandardError; end
|
||||
|
||||
class Base
|
||||
attr_internal :response_body
|
||||
attr_internal :action_name
|
||||
attr_internal :formats
|
||||
|
||||
class << self
|
||||
attr_reader :abstract
|
||||
@ -69,26 +72,43 @@ def action_methods
|
||||
# And always exclude explicitly hidden actions
|
||||
hidden_actions
|
||||
end
|
||||
|
||||
# Returns the full controller name, underscored, without the ending Controller.
|
||||
# For instance, MyApp::MyPostsController would return "my_app/my_posts" for
|
||||
# controller_name.
|
||||
#
|
||||
# ==== Returns
|
||||
# String
|
||||
def controller_path
|
||||
@controller_path ||= name && name.sub(/Controller$/, '').underscore
|
||||
end
|
||||
end
|
||||
|
||||
abstract!
|
||||
|
||||
# Calls the action going through the entire action dispatch stack.
|
||||
#
|
||||
#
|
||||
# The actual method that is called is determined by calling
|
||||
# #method_for_action. If no method can handle the action, then an
|
||||
# ActionNotFound error is raised.
|
||||
#
|
||||
# ==== Returns
|
||||
# self
|
||||
def process(action)
|
||||
def process(action, *args)
|
||||
@_action_name = action_name = action.to_s
|
||||
|
||||
unless action_name = method_for_action(action_name)
|
||||
raise ActionNotFound, "The action '#{action}' could not be found"
|
||||
end
|
||||
|
||||
process_action(action_name)
|
||||
@_response_body = nil
|
||||
|
||||
process_action(action_name, *args)
|
||||
end
|
||||
|
||||
# Delegates to the class' #controller_path
|
||||
def controller_path
|
||||
self.class.controller_path
|
||||
end
|
||||
|
||||
private
|
||||
@ -108,8 +128,8 @@ def action_method?(name)
|
||||
# Call the action. Override this in a subclass to modify the
|
||||
# behavior around processing an action. This, and not #process,
|
||||
# is the intended way to override action dispatching.
|
||||
def process_action(method_name)
|
||||
send_action(method_name)
|
||||
def process_action(method_name, *args)
|
||||
send_action(method_name, *args)
|
||||
end
|
||||
|
||||
# Actually call the method associated with the action. Override
|
||||
|
@ -1,12 +0,0 @@
|
||||
module AbstractController
|
||||
class Error < StandardError; end
|
||||
class ActionNotFound < StandardError; end
|
||||
|
||||
class DoubleRenderError < Error
|
||||
DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"."
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
end
|
||||
end
|
@ -4,7 +4,7 @@ module AbstractController
|
||||
module Helpers
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include RenderingController
|
||||
include Rendering
|
||||
|
||||
def self.next_serial
|
||||
@helper_serial ||= 0
|
||||
|
@ -1,8 +1,164 @@
|
||||
module AbstractController
|
||||
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
|
||||
# repeated setups. The inclusion pattern has pages that look like this:
|
||||
#
|
||||
# <%= render "shared/header" %>
|
||||
# Hello World
|
||||
# <%= render "shared/footer" %>
|
||||
#
|
||||
# This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
|
||||
# and if you ever want to change the structure of these two includes, you'll have to change all the templates.
|
||||
#
|
||||
# With layouts, you can flip it around and have the common structure know where to insert changing content. This means
|
||||
# that the header and footer are only mentioned in one place, like this:
|
||||
#
|
||||
# // The header part of this layout
|
||||
# <%= yield %>
|
||||
# // The footer part of this layout
|
||||
#
|
||||
# And then you have content pages that look like this:
|
||||
#
|
||||
# hello world
|
||||
#
|
||||
# At rendering time, the content page is computed and then inserted in the layout, like this:
|
||||
#
|
||||
# // The header part of this layout
|
||||
# hello world
|
||||
# // The footer part of this layout
|
||||
#
|
||||
# == Accessing shared variables
|
||||
#
|
||||
# Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
|
||||
# references that won't materialize before rendering time:
|
||||
#
|
||||
# <h1><%= @page_title %></h1>
|
||||
# <%= yield %>
|
||||
#
|
||||
# ...and content pages that fulfill these references _at_ rendering time:
|
||||
#
|
||||
# <% @page_title = "Welcome" %>
|
||||
# Off-world colonies offers you a chance to start a new life
|
||||
#
|
||||
# The result after rendering is:
|
||||
#
|
||||
# <h1>Welcome</h1>
|
||||
# Off-world colonies offers you a chance to start a new life
|
||||
#
|
||||
# == Layout assignment
|
||||
#
|
||||
# You can either specify a layout declaratively (using the #layout class method) or give
|
||||
# it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
|
||||
# If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
|
||||
#
|
||||
# For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
|
||||
# that template will be used for all actions in PostsController and controllers inheriting
|
||||
# from PostsController.
|
||||
#
|
||||
# If you use a module, for instance Weblog::PostsController, you will need a template named
|
||||
# <tt>app/views/layouts/weblog/posts.html.erb</tt>.
|
||||
#
|
||||
# Since all your controllers inherit from ApplicationController, they will use
|
||||
# <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
|
||||
# or provided.
|
||||
#
|
||||
# == Inheritance Examples
|
||||
#
|
||||
# class BankController < ActionController::Base
|
||||
# layout "bank_standard"
|
||||
#
|
||||
# class InformationController < BankController
|
||||
#
|
||||
# class TellerController < BankController
|
||||
# # teller.html.erb exists
|
||||
#
|
||||
# class TillController < TellerController
|
||||
#
|
||||
# class VaultController < BankController
|
||||
# layout :access_level_layout
|
||||
#
|
||||
# class EmployeeController < BankController
|
||||
# layout nil
|
||||
#
|
||||
# The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
|
||||
# and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
|
||||
#
|
||||
# The TellerController uses +teller.html.erb+, and TillController inherits that layout and
|
||||
# uses it as well.
|
||||
#
|
||||
# == Types of layouts
|
||||
#
|
||||
# Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
|
||||
# you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
|
||||
# be done either by specifying a method reference as a symbol or using an inline method (as a proc).
|
||||
#
|
||||
# The method reference is the preferred approach to variable layouts and is used like this:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout :writers_and_readers
|
||||
#
|
||||
# def index
|
||||
# # fetching posts
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def writers_and_readers
|
||||
# logged_in? ? "writer_layout" : "reader_layout"
|
||||
# end
|
||||
#
|
||||
# Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
|
||||
# is logged in or not.
|
||||
#
|
||||
# If you want to use an inline method, such as a proc, do something like this:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
|
||||
#
|
||||
# Of course, the most common way of specifying a layout is still just as a plain template name:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout "weblog_standard"
|
||||
#
|
||||
# If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
|
||||
# Otherwise, it will be looked up relative to the template root.
|
||||
#
|
||||
# == Conditional layouts
|
||||
#
|
||||
# If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
|
||||
# a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
|
||||
# <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout "weblog_standard", :except => :rss
|
||||
#
|
||||
# # ...
|
||||
#
|
||||
# end
|
||||
#
|
||||
# This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
|
||||
# around the rendered view.
|
||||
#
|
||||
# Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
|
||||
# #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
|
||||
#
|
||||
# == Using a different layout in the action render call
|
||||
#
|
||||
# If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
|
||||
# Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
|
||||
# You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout "weblog_standard"
|
||||
#
|
||||
# def help
|
||||
# render :action => "help", :layout => "help"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout.
|
||||
module Layouts
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include RenderingController
|
||||
include Rendering
|
||||
|
||||
included do
|
||||
extlib_inheritable_accessor(:_layout_conditions) { Hash.new }
|
||||
@ -20,7 +176,7 @@ def inherited(klass)
|
||||
end
|
||||
|
||||
def clear_template_caches!
|
||||
@found_layouts.clear if @found_layouts
|
||||
@found_layouts.clear if defined? @found_layouts
|
||||
super
|
||||
end
|
||||
|
||||
@ -89,7 +245,7 @@ def layout(layout, conditions = {})
|
||||
# ==== Returns
|
||||
# String:: A template name
|
||||
def _implied_layout_name
|
||||
name && name.underscore
|
||||
controller_path
|
||||
end
|
||||
|
||||
# Takes the specified layout and creates a _layout method to be called
|
||||
|
@ -1,6 +1,6 @@
|
||||
module AbstractController
|
||||
class HashKey
|
||||
@hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } }
|
||||
@hash_keys = Hash.new {|h,k| h[k] = Hash.new {|sh,sk| sh[sk] = {} } }
|
||||
|
||||
def self.get(klass, formats, locale)
|
||||
@hash_keys[klass][formats][locale] ||= new(klass, formats, locale)
|
||||
|
@ -9,53 +9,5 @@ module Logger
|
||||
cattr_accessor :logger
|
||||
extend ActiveSupport::Benchmarkable
|
||||
end
|
||||
|
||||
# A class that allows you to defer expensive processing
|
||||
# until the logger actually tries to log. Otherwise, you are
|
||||
# forced to do the processing in advance, and send the
|
||||
# entire processed String to the logger, which might
|
||||
# just discard the String if the log level is too low.
|
||||
#
|
||||
# TODO: Require that Rails loggers accept a block.
|
||||
class DelayedLog < ActiveSupport::BasicObject
|
||||
def initialize(&block)
|
||||
@str, @block = nil, block
|
||||
end
|
||||
|
||||
def method_missing(*args, &block)
|
||||
unless @str
|
||||
@str, @block = @block.call, nil
|
||||
end
|
||||
@str.send(*args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
# Override process_action in the AbstractController::Base
|
||||
# to log details about the method.
|
||||
def process_action(action)
|
||||
result = ActiveSupport::Notifications.instrument(:process_action,
|
||||
:controller => self, :action => action) do
|
||||
super
|
||||
end
|
||||
|
||||
if logger
|
||||
log = DelayedLog.new do
|
||||
"\n\nProcessing #{self.class.name}\##{action_name} " \
|
||||
"to #{request.formats} (for #{request_origin}) " \
|
||||
"[#{request.method.to_s.upcase}]"
|
||||
end
|
||||
|
||||
logger.info(log)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
# Returns the request origin with the IP and time. This needs to be cached,
|
||||
# otherwise we would get different results for each time it calls.
|
||||
def request_origin
|
||||
@request_origin ||= "#{request.remote_ip} at #{Time.now.to_s(:db)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,13 +1,18 @@
|
||||
require "abstract_controller/logger"
|
||||
require "abstract_controller/base"
|
||||
|
||||
module AbstractController
|
||||
module RenderingController
|
||||
class DoubleRenderError < Error
|
||||
DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"."
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
end
|
||||
|
||||
module Rendering
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include AbstractController::Logger
|
||||
|
||||
included do
|
||||
attr_internal :formats
|
||||
extlib_inheritable_accessor :_view_paths
|
||||
self._view_paths ||= ActionView::PathSet.new
|
||||
end
|
||||
@ -21,7 +26,7 @@ def initialize(*) #:nodoc:
|
||||
# An instance of a view class. The default view class is ActionView::Base
|
||||
#
|
||||
# The view class must have the following methods:
|
||||
# View.for_controller[controller] Create a new ActionView instance for a
|
||||
# View.for_controller[controller] Create a new ActionView instance for a
|
||||
# controller
|
||||
# View#render_partial[options]
|
||||
# - responsible for setting options[:_template]
|
||||
@ -59,7 +64,7 @@ def render(*args)
|
||||
def render_to_body(options = {})
|
||||
# TODO: Refactor so we can just use the normal template logic for this
|
||||
if options.key?(:partial)
|
||||
view_context.render_partial(options)
|
||||
_render_partial(options)
|
||||
else
|
||||
_determine_template(options)
|
||||
_render_template(options)
|
||||
@ -71,7 +76,7 @@ def render_to_body(options = {})
|
||||
#
|
||||
# :api: plugin
|
||||
def render_to_string(options = {})
|
||||
AbstractController::RenderingController.body_to_s(render_to_body(options))
|
||||
AbstractController::Rendering.body_to_s(render_to_body(options))
|
||||
end
|
||||
|
||||
# Renders the template from an object.
|
||||
@ -79,11 +84,18 @@ def render_to_string(options = {})
|
||||
# ==== Options
|
||||
# _template<ActionView::Template>:: The template to render
|
||||
# _layout<ActionView::Template>:: The layout to wrap the template in (optional)
|
||||
# _partial<TrueClass, FalseClass>:: Whether or not the template to be rendered is a partial
|
||||
def _render_template(options)
|
||||
view_context.render_template(options)
|
||||
end
|
||||
|
||||
# Renders the given partial.
|
||||
#
|
||||
# ==== Options
|
||||
# partial<String|Object>:: The partial name or the object to be rendered
|
||||
def _render_partial(options)
|
||||
view_context.render_partial(options)
|
||||
end
|
||||
|
||||
# The list of view paths for this controller. See ActionView::ViewPathSet for
|
||||
# more details about writing custom view paths.
|
||||
def view_paths
|
||||
@ -115,10 +127,10 @@ def self.body_to_s(body)
|
||||
# _partial<TrueClass, FalseClass>:: Whether or not the file to look up is a partial
|
||||
def _determine_template(options)
|
||||
if options.key?(:text)
|
||||
options[:_template] = ActionView::TextTemplate.new(options[:text], format_for_text)
|
||||
options[:_template] = ActionView::Template::Text.new(options[:text], format_for_text)
|
||||
elsif options.key?(:inline)
|
||||
handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb")
|
||||
template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
|
||||
handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb")
|
||||
template = ActionView::Template.new(options[:inline], "inline template", handler, {})
|
||||
options[:_template] = template
|
||||
elsif options.key?(:template)
|
||||
options[:_template_name] = options[:template]
|
||||
@ -152,12 +164,12 @@ def format_for_text
|
||||
module ClassMethods
|
||||
def clear_template_caches!
|
||||
end
|
||||
|
||||
|
||||
# Append a path to the list of view paths for this controller.
|
||||
#
|
||||
# ==== Parameters
|
||||
# path<String, ViewPath>:: If a String is provided, it gets converted into
|
||||
# the default view path. You may also provide a custom view path
|
||||
# path<String, ViewPath>:: If a String is provided, it gets converted into
|
||||
# the default view path. You may also provide a custom view path
|
||||
# (see ActionView::ViewPathSet for more information)
|
||||
def append_view_path(path)
|
||||
self.view_paths << path
|
||||
@ -166,8 +178,8 @@ def append_view_path(path)
|
||||
# Prepend a path to the list of view paths for this controller.
|
||||
#
|
||||
# ==== Parameters
|
||||
# path<String, ViewPath>:: If a String is provided, it gets converted into
|
||||
# the default view path. You may also provide a custom view path
|
||||
# path<String, ViewPath>:: If a String is provided, it gets converted into
|
||||
# the default view path. You may also provide a custom view path
|
||||
# (see ActionView::ViewPathSet for more information)
|
||||
def prepend_view_path(path)
|
||||
clear_template_caches!
|
||||
@ -186,9 +198,8 @@ def view_paths
|
||||
# otherwise, process the parameter into a ViewPathSet.
|
||||
def view_paths=(paths)
|
||||
clear_template_caches!
|
||||
self._view_paths = paths.is_a?(ActionView::PathSet) ?
|
||||
paths : ActionView::Base.process_view_paths(paths)
|
||||
self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,66 +1,76 @@
|
||||
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
|
||||
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
|
||||
require 'active_support/ruby/shim'
|
||||
|
||||
module ActionController
|
||||
autoload :Base, "action_controller/base"
|
||||
autoload :Benchmarking, "action_controller/metal/benchmarking"
|
||||
autoload :ConditionalGet, "action_controller/metal/conditional_get"
|
||||
autoload :Configuration, "action_controller/metal/configuration"
|
||||
autoload :Head, "action_controller/metal/head"
|
||||
autoload :Helpers, "action_controller/metal/helpers"
|
||||
autoload :HideActions, "action_controller/metal/hide_actions"
|
||||
autoload :Layouts, "action_controller/metal/layouts"
|
||||
autoload :Metal, "action_controller/metal"
|
||||
autoload :Middleware, "action_controller/middleware"
|
||||
autoload :RackConvenience, "action_controller/metal/rack_convenience"
|
||||
autoload :Rails2Compatibility, "action_controller/metal/compatibility"
|
||||
autoload :Redirector, "action_controller/metal/redirector"
|
||||
autoload :RenderingController, "action_controller/metal/rendering_controller"
|
||||
autoload :RenderOptions, "action_controller/metal/render_options"
|
||||
autoload :Rescue, "action_controller/metal/rescuable"
|
||||
autoload :Responder, "action_controller/metal/responder"
|
||||
autoload :Session, "action_controller/metal/session"
|
||||
autoload :Testing, "action_controller/metal/testing"
|
||||
autoload :UrlFor, "action_controller/metal/url_for"
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Caching, 'action_controller/caching'
|
||||
autoload :Dispatcher, 'action_controller/dispatch/dispatcher'
|
||||
autoload :Integration, 'action_controller/deprecated/integration_test'
|
||||
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
|
||||
autoload :MimeResponds, 'action_controller/metal/mime_responds'
|
||||
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
|
||||
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
|
||||
autoload :RecordIdentifier, 'action_controller/record_identifier'
|
||||
autoload :Routing, 'action_controller/deprecated'
|
||||
autoload :SessionManagement, 'action_controller/metal/session_management'
|
||||
autoload :TestCase, 'action_controller/testing/test_case'
|
||||
autoload :TestProcess, 'action_controller/testing/process'
|
||||
autoload :UrlRewriter, 'action_controller/url_rewriter'
|
||||
autoload :UrlWriter, 'action_controller/url_rewriter'
|
||||
autoload :Base
|
||||
autoload :Caching
|
||||
autoload :PolymorphicRoutes
|
||||
autoload :Translation
|
||||
autoload :Metal
|
||||
autoload :Middleware
|
||||
|
||||
autoload :Verification, 'action_controller/metal/verification'
|
||||
autoload :Flash, 'action_controller/metal/flash'
|
||||
autoload :RequestForgeryProtection, 'action_controller/metal/request_forgery_protection'
|
||||
autoload :Streaming, 'action_controller/metal/streaming'
|
||||
autoload :HttpAuthentication, 'action_controller/metal/http_authentication'
|
||||
autoload :FilterParameterLogging, 'action_controller/metal/filter_parameter_logging'
|
||||
autoload :Translation, 'action_controller/translation'
|
||||
autoload :Cookies, 'action_controller/metal/cookies'
|
||||
autoload_under "metal" do
|
||||
autoload :Compatibility
|
||||
autoload :ConditionalGet
|
||||
autoload :Configuration
|
||||
autoload :Cookies
|
||||
autoload :FilterParameterLogging
|
||||
autoload :Flash
|
||||
autoload :Head
|
||||
autoload :Helpers
|
||||
autoload :HideActions
|
||||
autoload :HttpAuthentication
|
||||
autoload :Logger
|
||||
autoload :MimeResponds
|
||||
autoload :RackDelegation
|
||||
autoload :Redirecting
|
||||
autoload :Rendering
|
||||
autoload :Renderers
|
||||
autoload :RequestForgeryProtection
|
||||
autoload :Rescue
|
||||
autoload :Responder
|
||||
autoload :SessionManagement
|
||||
autoload :Streaming
|
||||
autoload :UrlFor
|
||||
autoload :Verification
|
||||
end
|
||||
|
||||
autoload :ActionControllerError, 'action_controller/metal/exceptions'
|
||||
autoload :RenderError, 'action_controller/metal/exceptions'
|
||||
autoload :RoutingError, 'action_controller/metal/exceptions'
|
||||
autoload :MethodNotAllowed, 'action_controller/metal/exceptions'
|
||||
autoload :NotImplemented, 'action_controller/metal/exceptions'
|
||||
autoload :UnknownController, 'action_controller/metal/exceptions'
|
||||
autoload :MissingFile, 'action_controller/metal/exceptions'
|
||||
autoload :RenderError, 'action_controller/metal/exceptions'
|
||||
autoload :SessionOverflowError, 'action_controller/metal/exceptions'
|
||||
autoload :UnknownHttpMethod, 'action_controller/metal/exceptions'
|
||||
autoload :Dispatcher, 'action_controller/dispatch/dispatcher'
|
||||
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
|
||||
autoload :Routing, 'action_controller/deprecated'
|
||||
autoload :Integration, 'action_controller/deprecated/integration_test'
|
||||
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
|
||||
|
||||
eager_autoload do
|
||||
autoload :RecordIdentifier
|
||||
autoload :UrlRewriter
|
||||
autoload :UrlWriter, 'action_controller/url_rewriter'
|
||||
|
||||
# TODO: Don't autoload exceptions, setup explicit
|
||||
# requires for files that need them
|
||||
autoload_at "action_controller/metal/exceptions" do
|
||||
autoload :ActionControllerError
|
||||
autoload :RenderError
|
||||
autoload :RoutingError
|
||||
autoload :MethodNotAllowed
|
||||
autoload :NotImplemented
|
||||
autoload :UnknownController
|
||||
autoload :MissingFile
|
||||
autoload :RenderError
|
||||
autoload :SessionOverflowError
|
||||
autoload :UnknownHttpMethod
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
autoload :HTML, 'action_controller/vendor/html-scanner'
|
||||
autoload :AbstractController, 'abstract_controller'
|
||||
|
||||
# All of these simply register additional autoloads
|
||||
require 'abstract_controller'
|
||||
require 'action_dispatch'
|
||||
require 'action_view'
|
||||
require 'action_controller/vendor/html-scanner'
|
||||
|
||||
# Common ActiveSupport usage in ActionController
|
||||
require "active_support/concern"
|
||||
|
@ -3,31 +3,28 @@ class Base < Metal
|
||||
abstract!
|
||||
|
||||
include AbstractController::Callbacks
|
||||
include AbstractController::Logger
|
||||
include AbstractController::Layouts
|
||||
|
||||
include ActionController::Helpers
|
||||
include ActionController::HideActions
|
||||
include ActionController::UrlFor
|
||||
include ActionController::Redirector
|
||||
include ActionController::RenderingController
|
||||
include ActionController::RenderOptions::All
|
||||
include ActionController::Layouts
|
||||
include ActionController::Redirecting
|
||||
include ActionController::Rendering
|
||||
include ActionController::Renderers::All
|
||||
include ActionController::ConditionalGet
|
||||
include ActionController::RackConvenience
|
||||
include ActionController::Benchmarking
|
||||
include ActionController::RackDelegation
|
||||
include ActionController::Logger
|
||||
include ActionController::Configuration
|
||||
|
||||
# Legacy modules
|
||||
include SessionManagement
|
||||
include ActionDispatch::StatusCodes
|
||||
include ActionController::Caching
|
||||
include ActionController::MimeResponds
|
||||
|
||||
# Rails 2.x compatibility
|
||||
include ActionController::Rails2Compatibility
|
||||
include ActionController::Compatibility
|
||||
|
||||
include ActionController::Cookies
|
||||
include ActionController::Session
|
||||
include ActionController::Flash
|
||||
include ActionController::Verification
|
||||
include ActionController::RequestForgeryProtection
|
||||
@ -91,7 +88,7 @@ def _normalize_options(action = nil, options = {}, &blk)
|
||||
end
|
||||
|
||||
if options[:status]
|
||||
options[:status] = interpret_status(options[:status]).to_i
|
||||
options[:status] = Rack::Utils.status_code(options[:status])
|
||||
end
|
||||
|
||||
options[:update] = blk if block_given?
|
||||
@ -107,62 +104,5 @@ def render_to_string(action = nil, options = {}, &blk)
|
||||
options = _normalize_options(action, options, &blk)
|
||||
super(options)
|
||||
end
|
||||
|
||||
# Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
|
||||
#
|
||||
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
||||
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
|
||||
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) - Is passed straight through as the target for redirection.
|
||||
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
|
||||
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
|
||||
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
|
||||
#
|
||||
# Examples:
|
||||
# redirect_to :action => "show", :id => 5
|
||||
# redirect_to post
|
||||
# redirect_to "http://www.rubyonrails.org"
|
||||
# redirect_to "/images/screenshot.jpg"
|
||||
# redirect_to articles_url
|
||||
# redirect_to :back
|
||||
#
|
||||
# The redirection happens as a "302 Moved" header unless otherwise specified.
|
||||
#
|
||||
# Examples:
|
||||
# redirect_to post_url(@post), :status=>:found
|
||||
# redirect_to :action=>'atom', :status=>:moved_permanently
|
||||
# redirect_to post_url(@post), :status=>301
|
||||
# redirect_to :action=>'atom', :status=>302
|
||||
#
|
||||
# When using <tt>redirect_to :back</tt>, if there is no referrer,
|
||||
# RedirectBackError will be raised. You may specify some fallback
|
||||
# behavior for this case by rescuing RedirectBackError.
|
||||
def redirect_to(options = {}, response_status = {}) #:doc:
|
||||
raise ActionControllerError.new("Cannot redirect to nil!") if options.nil?
|
||||
|
||||
status = if options.is_a?(Hash) && options.key?(:status)
|
||||
interpret_status(options.delete(:status))
|
||||
elsif response_status.key?(:status)
|
||||
interpret_status(response_status[:status])
|
||||
else
|
||||
302
|
||||
end
|
||||
|
||||
url = case options
|
||||
# The scheme name consist of a letter followed by any combination of
|
||||
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
|
||||
# characters; and is terminated by a colon (":").
|
||||
when %r{^\w[\w\d+.-]*:.*}
|
||||
options
|
||||
when String
|
||||
request.protocol + request.host_with_port + options
|
||||
when :back
|
||||
raise RedirectBackError unless refer = request.headers["Referer"]
|
||||
refer
|
||||
else
|
||||
url_for(options)
|
||||
end
|
||||
|
||||
super(url, status)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -30,12 +30,15 @@ module ActionController #:nodoc:
|
||||
# config.action_controller.cache_store = MyOwnStore.new("parameter")
|
||||
module Caching
|
||||
extend ActiveSupport::Concern
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Actions, 'action_controller/caching/actions'
|
||||
autoload :Fragments, 'action_controller/caching/fragments'
|
||||
autoload :Pages, 'action_controller/caching/pages'
|
||||
autoload :Sweeper, 'action_controller/caching/sweeping'
|
||||
autoload :Sweeping, 'action_controller/caching/sweeping'
|
||||
eager_autoload do
|
||||
autoload :Actions
|
||||
autoload :Fragments
|
||||
autoload :Pages
|
||||
autoload :Sweeper, 'action_controller/caching/sweeping'
|
||||
autoload :Sweeping, 'action_controller/caching/sweeping'
|
||||
end
|
||||
|
||||
included do
|
||||
@@cache_store = nil
|
||||
@ -57,6 +60,17 @@ module ClassMethods
|
||||
def cache_configured?
|
||||
perform_caching && cache_store
|
||||
end
|
||||
|
||||
def log_event(name, before, after, instrumenter_id, payload)
|
||||
if name.to_s =~ /(read|write|cache|expire|exist)_(fragment|page)\??/
|
||||
key_or_path = payload[:key] || payload[:path]
|
||||
human_name = name.to_s.humanize
|
||||
duration = (after - before) * 1000
|
||||
logger.info("#{human_name} #{key_or_path.inspect} (%.1fms)" % duration)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def caching_allowed?
|
||||
|
@ -74,7 +74,7 @@ def fragment_exist?(key, options = nil)
|
||||
return unless cache_configured?
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
ActiveSupport::Notifications.instrument(:fragment_exist?, :key => key) do
|
||||
ActiveSupport::Notifications.instrument(:exist_fragment?, :key => key) do
|
||||
cache_store.exist?(key, options)
|
||||
end
|
||||
end
|
||||
|
@ -13,11 +13,6 @@ def define_dispatcher_callbacks(cache_classes)
|
||||
# Run prepare callbacks before every request in development mode
|
||||
self.prepare_each_request = true
|
||||
|
||||
# Development mode callbacks
|
||||
ActionDispatch::Callbacks.before_dispatch do |app|
|
||||
ActionController::Routing::Routes.reload
|
||||
end
|
||||
|
||||
ActionDispatch::Callbacks.after_dispatch do
|
||||
# Cleanup the application before processing the current request.
|
||||
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
|
||||
|
@ -28,24 +28,9 @@ def controller_name
|
||||
self.class.controller_name
|
||||
end
|
||||
|
||||
# Returns the full controller name, underscored, without the ending Controller.
|
||||
# For instance, MyApp::MyPostsController would return "my_app/my_posts" for
|
||||
# controller_name.
|
||||
#
|
||||
# ==== Returns
|
||||
# String
|
||||
def self.controller_path
|
||||
@controller_path ||= name && name.sub(/Controller$/, '').underscore
|
||||
end
|
||||
|
||||
# Delegates to the class' #controller_path
|
||||
def controller_path
|
||||
self.class.controller_path
|
||||
end
|
||||
|
||||
# The details below can be overridden to support a specific
|
||||
# Request and Response object. The default ActionController::Base
|
||||
# implementation includes RackConvenience, which makes a request
|
||||
# implementation includes RackDelegation, which makes a request
|
||||
# and response object available. You might wish to control the
|
||||
# environment and response manually for performance reasons.
|
||||
|
||||
@ -57,7 +42,7 @@ def initialize(*)
|
||||
end
|
||||
|
||||
# Basic implementations for content_type=, location=, and headers are
|
||||
# provided to reduce the dependency on the RackConvenience module
|
||||
# provided to reduce the dependency on the RackDelegation module
|
||||
# in Renderer and Redirector.
|
||||
|
||||
def content_type=(type)
|
||||
@ -68,6 +53,10 @@ def location=(url)
|
||||
headers["Location"] = url
|
||||
end
|
||||
|
||||
def status=(status)
|
||||
@_status = Rack::Utils.status_code(status)
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def dispatch(name, env)
|
||||
@_env = env
|
||||
@ -81,7 +70,7 @@ def to_a
|
||||
end
|
||||
|
||||
class ActionEndpoint
|
||||
@@endpoints = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } }
|
||||
@@endpoints = Hash.new {|h,k| h[k] = Hash.new {|sh,sk| sh[sk] = {} } }
|
||||
|
||||
def self.for(controller, action, stack)
|
||||
@@endpoints[controller][action][stack] ||= begin
|
||||
@ -92,6 +81,7 @@ def self.for(controller, action, stack)
|
||||
|
||||
def initialize(controller, action)
|
||||
@controller, @action = controller, action
|
||||
@_formats = [Mime::HTML]
|
||||
end
|
||||
|
||||
def call(env)
|
||||
|
@ -1,73 +0,0 @@
|
||||
require 'active_support/core_ext/benchmark'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# The benchmarking module times the performance of actions and reports to the logger. If the Active Record
|
||||
# package has been included, a separate timing section for database calls will be added as well.
|
||||
module Benchmarking #:nodoc:
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
protected
|
||||
def render(*args, &block)
|
||||
if logger
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
||||
end
|
||||
|
||||
render_output = nil
|
||||
@view_runtime = Benchmark.ms { render_output = super }
|
||||
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
@db_rt_before_render = db_runtime
|
||||
@db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
|
||||
@view_runtime -= @db_rt_after_render
|
||||
end
|
||||
|
||||
render_output
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def process_action(*args)
|
||||
if logger
|
||||
ms = [Benchmark.ms { super }, 0.01].max
|
||||
logging_view = defined?(@view_runtime)
|
||||
logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
|
||||
log_message = 'Completed in %.0fms' % ms
|
||||
|
||||
if logging_view || logging_active_record
|
||||
log_message << " ("
|
||||
log_message << view_runtime if logging_view
|
||||
|
||||
if logging_active_record
|
||||
log_message << ", " if logging_view
|
||||
log_message << active_record_runtime + ")"
|
||||
else
|
||||
")"
|
||||
end
|
||||
end
|
||||
|
||||
log_message << " | #{response.status}"
|
||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
||||
|
||||
logger.info(log_message)
|
||||
response.headers["X-Runtime"] = "%.0f" % ms
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def view_runtime
|
||||
"View: %.0f" % @view_runtime
|
||||
end
|
||||
|
||||
def active_record_runtime
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
||||
db_runtime += @db_rt_before_render if @db_rt_before_render
|
||||
db_runtime += @db_rt_after_render if @db_rt_after_render
|
||||
"DB: %.0f" % db_runtime
|
||||
end
|
||||
end
|
||||
end
|
@ -1,5 +1,5 @@
|
||||
module ActionController
|
||||
module Rails2Compatibility
|
||||
module Compatibility
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class ::ActionController::ActionControllerError < StandardError #:nodoc:
|
||||
@ -46,11 +46,8 @@ class << self
|
||||
cattr_accessor :use_accept_header
|
||||
self.use_accept_header = true
|
||||
|
||||
cattr_accessor :page_cache_directory
|
||||
self.page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
|
||||
|
||||
cattr_reader :cache_store
|
||||
|
||||
cattr_accessor :consider_all_requests_local
|
||||
self.consider_all_requests_local = true
|
||||
|
||||
@ -116,7 +113,7 @@ def _find_layout(name, details)
|
||||
details[:prefix] = nil if name =~ /\blayouts/
|
||||
super
|
||||
end
|
||||
|
||||
|
||||
# Move this into a "don't run in production" module
|
||||
def _default_layout(details, require_layout = false)
|
||||
super
|
||||
|
@ -2,7 +2,7 @@ module ActionController
|
||||
module ConditionalGet
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include RackConvenience
|
||||
include RackDelegation
|
||||
include Head
|
||||
|
||||
# Sets the etag, last_modified, or both on the response and renders a
|
||||
|
@ -46,17 +46,18 @@ module ActionController #:nodoc:
|
||||
module Cookies
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include RackConvenience
|
||||
include RackDelegation
|
||||
|
||||
included do
|
||||
helper_method :cookies
|
||||
cattr_accessor :cookie_verifier_secret
|
||||
end
|
||||
|
||||
protected
|
||||
# Returns the cookie container, which operates as described above.
|
||||
def cookies
|
||||
@cookies ||= CookieJar.build(request, response)
|
||||
end
|
||||
protected
|
||||
# Returns the cookie container, which operates as described above.
|
||||
def cookies
|
||||
@cookies ||= CookieJar.build(request, response)
|
||||
end
|
||||
end
|
||||
|
||||
class CookieJar < Hash #:nodoc:
|
||||
@ -86,7 +87,7 @@ def []=(key, options)
|
||||
end
|
||||
|
||||
super(key.to_s, value)
|
||||
|
||||
|
||||
options[:path] ||= "/"
|
||||
response.set_cookie(key, options)
|
||||
end
|
||||
@ -101,5 +102,96 @@ def delete(key, options = {})
|
||||
response.delete_cookie(key, options)
|
||||
value
|
||||
end
|
||||
|
||||
# Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
|
||||
#
|
||||
# cookies.permanent[:prefers_open_id] = true
|
||||
# # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
||||
#
|
||||
# This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
|
||||
#
|
||||
# This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
|
||||
#
|
||||
# cookies.permanent.signed[:remember_me] = current_user.id
|
||||
# # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
|
||||
def permanent
|
||||
@permanent ||= PermanentCookieJar.new(self)
|
||||
end
|
||||
|
||||
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
|
||||
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
|
||||
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
|
||||
# be raised.
|
||||
#
|
||||
# This jar requires that you set a suitable secret for the verification on ActionController::Base.cookie_verifier_secret.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# cookies.signed[:discount] = 45
|
||||
# # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
|
||||
#
|
||||
# cookies.signed[:discount] # => 45
|
||||
def signed
|
||||
@signed ||= SignedCookieJar.new(self)
|
||||
end
|
||||
end
|
||||
|
||||
class PermanentCookieJar < CookieJar #:nodoc:
|
||||
def initialize(parent_jar)
|
||||
@parent_jar = parent_jar
|
||||
end
|
||||
|
||||
def []=(key, options)
|
||||
if options.is_a?(Hash)
|
||||
options.symbolize_keys!
|
||||
else
|
||||
options = { :value => options }
|
||||
end
|
||||
|
||||
options[:expires] = 20.years.from_now
|
||||
@parent_jar[key] = options
|
||||
end
|
||||
|
||||
def signed
|
||||
@signed ||= SignedCookieJar.new(self)
|
||||
end
|
||||
|
||||
def controller
|
||||
@parent_jar.controller
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments, &block)
|
||||
@parent_jar.send(method, *arguments, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class SignedCookieJar < CookieJar #:nodoc:
|
||||
def initialize(parent_jar)
|
||||
unless ActionController::Base.cookie_verifier_secret
|
||||
raise "You must set ActionController::Base.cookie_verifier_secret to use signed cookies"
|
||||
end
|
||||
|
||||
@parent_jar = parent_jar
|
||||
@verifier = ActiveSupport::MessageVerifier.new(ActionController::Base.cookie_verifier_secret)
|
||||
end
|
||||
|
||||
def [](name)
|
||||
@verifier.verify(@parent_jar[name])
|
||||
end
|
||||
|
||||
def []=(key, options)
|
||||
if options.is_a?(Hash)
|
||||
options.symbolize_keys!
|
||||
options[:value] = @verifier.generate(options[:value])
|
||||
else
|
||||
options = { :value => @verifier.generate(options) }
|
||||
end
|
||||
|
||||
@parent_jar[key] = options
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments, &block)
|
||||
@parent_jar.send(method, *arguments, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -18,18 +18,9 @@ class MethodNotAllowed < ActionControllerError #:nodoc:
|
||||
|
||||
def initialize(*allowed_methods)
|
||||
super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
|
||||
@allowed_methods = allowed_methods
|
||||
end
|
||||
|
||||
def allowed_methods_header
|
||||
allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', '
|
||||
end
|
||||
|
||||
def handle_response!(response)
|
||||
response.headers['Allow'] ||= allowed_methods_header
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class NotImplemented < MethodNotAllowed #:nodoc:
|
||||
end
|
||||
|
||||
|
@ -2,8 +2,6 @@ module ActionController
|
||||
module FilterParameterLogging
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include AbstractController::Logger
|
||||
|
||||
module ClassMethods
|
||||
# Replace sensitive parameter data from the request log.
|
||||
# Filters parameters that have any of the arguments as a substring.
|
||||
@ -54,23 +52,25 @@ def filter_parameter_logging(*filter_words, &block)
|
||||
end
|
||||
protected :filter_parameters
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Overwrite log_process_action to include parameters information.
|
||||
# If this method is invoked, it means logger is defined, so don't
|
||||
# worry with such scenario here.
|
||||
def log_process_action(controller) #:nodoc:
|
||||
params = controller.send(:filter_parameters, controller.request.params)
|
||||
logger.info " Parameters: #{params.inspect}" unless params.empty?
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
INTERNAL_PARAMS = [:controller, :action, :format, :_method, :only_path]
|
||||
|
||||
def process(*)
|
||||
response = super
|
||||
if logger
|
||||
parameters = filter_parameters(params).except!(*INTERNAL_PARAMS)
|
||||
logger.info { " Parameters: #{parameters.inspect}" } unless parameters.empty?
|
||||
end
|
||||
response
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def filter_parameters(params)
|
||||
params.dup
|
||||
params.dup.except!(*INTERNAL_PARAMS)
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -28,7 +28,9 @@ module ActionController #:nodoc:
|
||||
module Flash
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include Session
|
||||
included do
|
||||
helper_method :alert, :notice
|
||||
end
|
||||
|
||||
class FlashNow #:nodoc:
|
||||
def initialize(flash)
|
||||
@ -121,30 +123,18 @@ def store(session)
|
||||
session["flash"] = self
|
||||
end
|
||||
|
||||
private
|
||||
# Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
|
||||
# use() # marks the entire flash as used
|
||||
# use('msg') # marks the "msg" entry as used
|
||||
# use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
|
||||
# use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
|
||||
# Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
|
||||
# if no key is passed.
|
||||
def use(key = nil, used = true)
|
||||
Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
|
||||
return key ? self[key] : self
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def process_action(method_name)
|
||||
super
|
||||
@_flash.store(session) if @_flash
|
||||
@_flash = nil
|
||||
end
|
||||
|
||||
def reset_session
|
||||
super
|
||||
@_flash = nil
|
||||
private
|
||||
# Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
|
||||
# use() # marks the entire flash as used
|
||||
# use('msg') # marks the "msg" entry as used
|
||||
# use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
|
||||
# use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
|
||||
# Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
|
||||
# if no key is passed.
|
||||
def use(key = nil, used = true)
|
||||
Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
|
||||
return key ? self[key] : self
|
||||
end
|
||||
end
|
||||
|
||||
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
|
||||
@ -158,5 +148,54 @@ def flash #:doc:
|
||||
|
||||
@_flash
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:alert]
|
||||
def alert
|
||||
flash[:alert]
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:alert]=
|
||||
def alert=(message)
|
||||
flash[:alert] = message
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:notice]
|
||||
def notice
|
||||
flash[:notice]
|
||||
end
|
||||
|
||||
# Convenience accessor for flash[:notice]=
|
||||
def notice=(message)
|
||||
flash[:notice] = message
|
||||
end
|
||||
|
||||
protected
|
||||
def process_action(method_name)
|
||||
@_flash = nil
|
||||
super
|
||||
@_flash.store(session) if @_flash
|
||||
@_flash = nil
|
||||
end
|
||||
|
||||
def reset_session
|
||||
super
|
||||
@_flash = nil
|
||||
end
|
||||
|
||||
def redirect_to(options = {}, response_status_and_flash = {}) #:doc:
|
||||
if alert = response_status_and_flash.delete(:alert)
|
||||
flash[:alert] = alert
|
||||
end
|
||||
|
||||
if notice = response_status_and_flash.delete(:notice)
|
||||
flash[:notice] = notice
|
||||
end
|
||||
|
||||
if other_flashes = response_status_and_flash.delete(:flash)
|
||||
flash.update(other_flashes)
|
||||
end
|
||||
|
||||
super(options, response_status_and_flash)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,7 @@
|
||||
module ActionController
|
||||
module Head
|
||||
include UrlFor
|
||||
|
||||
# Return a response that has no content (merely headers). The options
|
||||
# argument is interpreted to be a hash of header names and values.
|
||||
# This allows you to easily return a response that consists only of
|
||||
@ -21,7 +23,10 @@ def head(status, options = {})
|
||||
headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s
|
||||
end
|
||||
|
||||
render :nothing => true, :status => status, :location => location
|
||||
self.status = status
|
||||
self.location = url_for(location) if location
|
||||
self.content_type = Mime[formats.first]
|
||||
self.response_body = " "
|
||||
end
|
||||
end
|
||||
end
|
@ -52,7 +52,7 @@ module Helpers
|
||||
included do
|
||||
# Set the default directory for helpers
|
||||
extlib_inheritable_accessor(:helpers_dir) do
|
||||
defined?(Rails) ? "#{Rails.root}/app/helpers" : "app/helpers"
|
||||
defined?(Rails.root) ? "#{Rails.root}/app/helpers" : "app/helpers"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,171 +0,0 @@
|
||||
module ActionController
|
||||
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
|
||||
# repeated setups. The inclusion pattern has pages that look like this:
|
||||
#
|
||||
# <%= render "shared/header" %>
|
||||
# Hello World
|
||||
# <%= render "shared/footer" %>
|
||||
#
|
||||
# This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
|
||||
# and if you ever want to change the structure of these two includes, you'll have to change all the templates.
|
||||
#
|
||||
# With layouts, you can flip it around and have the common structure know where to insert changing content. This means
|
||||
# that the header and footer are only mentioned in one place, like this:
|
||||
#
|
||||
# // The header part of this layout
|
||||
# <%= yield %>
|
||||
# // The footer part of this layout
|
||||
#
|
||||
# And then you have content pages that look like this:
|
||||
#
|
||||
# hello world
|
||||
#
|
||||
# At rendering time, the content page is computed and then inserted in the layout, like this:
|
||||
#
|
||||
# // The header part of this layout
|
||||
# hello world
|
||||
# // The footer part of this layout
|
||||
#
|
||||
# == Accessing shared variables
|
||||
#
|
||||
# Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
|
||||
# references that won't materialize before rendering time:
|
||||
#
|
||||
# <h1><%= @page_title %></h1>
|
||||
# <%= yield %>
|
||||
#
|
||||
# ...and content pages that fulfill these references _at_ rendering time:
|
||||
#
|
||||
# <% @page_title = "Welcome" %>
|
||||
# Off-world colonies offers you a chance to start a new life
|
||||
#
|
||||
# The result after rendering is:
|
||||
#
|
||||
# <h1>Welcome</h1>
|
||||
# Off-world colonies offers you a chance to start a new life
|
||||
#
|
||||
# == Layout assignment
|
||||
#
|
||||
# You can either specify a layout declaratively (using the #layout class method) or give
|
||||
# it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
|
||||
# If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
|
||||
#
|
||||
# For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
|
||||
# that template will be used for all actions in PostsController and controllers inheriting
|
||||
# from PostsController.
|
||||
#
|
||||
# If you use a module, for instance Weblog::PostsController, you will need a template named
|
||||
# <tt>app/views/layouts/weblog/posts.html.erb</tt>.
|
||||
#
|
||||
# Since all your controllers inherit from ApplicationController, they will use
|
||||
# <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
|
||||
# or provided.
|
||||
#
|
||||
# == Inheritance Examples
|
||||
#
|
||||
# class BankController < ActionController::Base
|
||||
# layout "bank_standard"
|
||||
#
|
||||
# class InformationController < BankController
|
||||
#
|
||||
# class TellerController < BankController
|
||||
# # teller.html.erb exists
|
||||
#
|
||||
# class TillController < TellerController
|
||||
#
|
||||
# class VaultController < BankController
|
||||
# layout :access_level_layout
|
||||
#
|
||||
# class EmployeeController < BankController
|
||||
# layout nil
|
||||
#
|
||||
# The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
|
||||
# and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
|
||||
#
|
||||
# The TellerController uses +teller.html.erb+, and TillController inherits that layout and
|
||||
# uses it as well.
|
||||
#
|
||||
# == Types of layouts
|
||||
#
|
||||
# Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
|
||||
# you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
|
||||
# be done either by specifying a method reference as a symbol or using an inline method (as a proc).
|
||||
#
|
||||
# The method reference is the preferred approach to variable layouts and is used like this:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout :writers_and_readers
|
||||
#
|
||||
# def index
|
||||
# # fetching posts
|
||||
# end
|
||||
#
|
||||
# private
|
||||
# def writers_and_readers
|
||||
# logged_in? ? "writer_layout" : "reader_layout"
|
||||
# end
|
||||
#
|
||||
# Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
|
||||
# is logged in or not.
|
||||
#
|
||||
# If you want to use an inline method, such as a proc, do something like this:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
|
||||
#
|
||||
# Of course, the most common way of specifying a layout is still just as a plain template name:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout "weblog_standard"
|
||||
#
|
||||
# If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
|
||||
# Otherwise, it will be looked up relative to the template root.
|
||||
#
|
||||
# == Conditional layouts
|
||||
#
|
||||
# If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
|
||||
# a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
|
||||
# <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout "weblog_standard", :except => :rss
|
||||
#
|
||||
# # ...
|
||||
#
|
||||
# end
|
||||
#
|
||||
# This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
|
||||
# around the rendered view.
|
||||
#
|
||||
# Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
|
||||
# #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
|
||||
#
|
||||
# == Using a different layout in the action render call
|
||||
#
|
||||
# If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
|
||||
# Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
|
||||
# You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# layout "weblog_standard"
|
||||
#
|
||||
# def help
|
||||
# render :action => "help", :layout => "help"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout.
|
||||
module Layouts
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include ActionController::RenderingController
|
||||
include AbstractController::Layouts
|
||||
|
||||
module ClassMethods
|
||||
# If no layout is provided, look for a layout with this name.
|
||||
def _implied_layout_name
|
||||
controller_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
89
actionpack/lib/action_controller/metal/logger.rb
Normal file
89
actionpack/lib/action_controller/metal/logger.rb
Normal file
@ -0,0 +1,89 @@
|
||||
require 'abstract_controller/logger'
|
||||
|
||||
module ActionController
|
||||
# Adds instrumentation to <tt>process_action</tt> and a <tt>log_event</tt> method
|
||||
# responsible to log events from ActiveSupport::Notifications. This module handles
|
||||
# :process_action and :render_template events but allows any other module to hook
|
||||
# into log_event and provide its own logging facilities (as in ActionController::Caching).
|
||||
module Logger
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include AbstractController::Logger
|
||||
end
|
||||
|
||||
attr_internal :view_runtime
|
||||
|
||||
def process_action(action)
|
||||
ActiveSupport::Notifications.instrument(:process_action, :controller => self, :action => action) do
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def render(*args, &block)
|
||||
if logger
|
||||
render_output = nil
|
||||
|
||||
self.view_runtime = cleanup_view_runtime do
|
||||
Benchmark.ms { render_output = super }
|
||||
end
|
||||
|
||||
render_output
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# If you want to remove any time taken into account in :view_runtime
|
||||
# wrongly, you can do it here:
|
||||
#
|
||||
# def cleanup_view_runtime
|
||||
# super - time_taken_in_something_expensive
|
||||
# end
|
||||
#
|
||||
# :api: plugin
|
||||
def cleanup_view_runtime #:nodoc:
|
||||
yield
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# This is the hook invoked by ActiveSupport::Notifications.subscribe.
|
||||
# If you need to log any event, overwrite the method and do it here.
|
||||
def log_event(name, before, after, instrumenter_id, payload) #:nodoc:
|
||||
if name == :process_action
|
||||
duration = [(after - before) * 1000, 0.01].max
|
||||
controller = payload[:controller]
|
||||
request = controller.request
|
||||
|
||||
logger.info "\n\nProcessed #{controller.class.name}##{payload[:action]} " \
|
||||
"to #{request.formats} (for #{request.remote_ip} at #{before.to_s(:db)}) " \
|
||||
"[#{request.method.to_s.upcase}]"
|
||||
|
||||
log_process_action(controller)
|
||||
|
||||
message = "Completed in %.0fms" % duration
|
||||
message << " | #{controller.response.status}"
|
||||
message << " [#{request.request_uri rescue "unknown"}]"
|
||||
|
||||
logger.info(message)
|
||||
elsif name == :render_template
|
||||
# TODO Make render_template logging work if you are using just ActionView
|
||||
duration = (after - before) * 1000
|
||||
message = "Rendered #{payload[:identifier]}"
|
||||
message << " within #{payload[:layout]}" if payload[:layout]
|
||||
message << (" (%.1fms)" % duration)
|
||||
logger.info(message)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# A hook which allows logging what happened during controller process action.
|
||||
# :api: plugin
|
||||
def log_process_action(controller) #:nodoc:
|
||||
view_runtime = controller.send :view_runtime
|
||||
logger.info(" View runtime: %.1fms" % view_runtime.to_f) if view_runtime
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,8 +1,12 @@
|
||||
require 'action_dispatch/http/request'
|
||||
require 'action_dispatch/http/response'
|
||||
|
||||
module ActionController
|
||||
module RackConvenience
|
||||
module RackDelegation
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
delegate :session, :to => "@_request"
|
||||
delegate :headers, :status=, :location=, :content_type=,
|
||||
:status, :location, :content_type, :to => "@_response"
|
||||
attr_internal :request
|
||||
@ -23,5 +27,9 @@ def response_body=(body)
|
||||
response.body = body if response
|
||||
super
|
||||
end
|
||||
|
||||
def reset_session
|
||||
@_request.reset_session
|
||||
end
|
||||
end
|
||||
end
|
90
actionpack/lib/action_controller/metal/redirecting.rb
Normal file
90
actionpack/lib/action_controller/metal/redirecting.rb
Normal file
@ -0,0 +1,90 @@
|
||||
module ActionController
|
||||
class RedirectBackError < AbstractController::Error #:nodoc:
|
||||
DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].'
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
end
|
||||
|
||||
module Redirecting
|
||||
extend ActiveSupport::Concern
|
||||
include AbstractController::Logger
|
||||
|
||||
# Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
|
||||
#
|
||||
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
||||
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
|
||||
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) - Is passed straight through as the target for redirection.
|
||||
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
|
||||
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
|
||||
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
|
||||
#
|
||||
# Examples:
|
||||
# redirect_to :action => "show", :id => 5
|
||||
# redirect_to post
|
||||
# redirect_to "http://www.rubyonrails.org"
|
||||
# redirect_to "/images/screenshot.jpg"
|
||||
# redirect_to articles_url
|
||||
# redirect_to :back
|
||||
#
|
||||
# The redirection happens as a "302 Moved" header unless otherwise specified.
|
||||
#
|
||||
# Examples:
|
||||
# redirect_to post_url(@post), :status => :found
|
||||
# redirect_to :action=>'atom', :status => :moved_permanently
|
||||
# redirect_to post_url(@post), :status => 301
|
||||
# redirect_to :action=>'atom', :status => 302
|
||||
#
|
||||
# It is also possible to assign a flash message as part of the redirection. There are two special accessors for commonly used the flash names
|
||||
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
|
||||
#
|
||||
# Examples:
|
||||
# redirect_to post_url(@post), :alert => "Watch it, mister!"
|
||||
# redirect_to post_url(@post), :status=> :found, :notice => "Pay attention to the road"
|
||||
# redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id }
|
||||
# redirect_to { :action=>'atom' }, :alert => "Something serious happened"
|
||||
#
|
||||
# When using <tt>redirect_to :back</tt>, if there is no referrer,
|
||||
# RedirectBackError will be raised. You may specify some fallback
|
||||
# behavior for this case by rescuing RedirectBackError.
|
||||
def redirect_to(options = {}, response_status = {}) #:doc:
|
||||
raise ActionControllerError.new("Cannot redirect to nil!") if options.nil?
|
||||
raise AbstractController::DoubleRenderError if response_body
|
||||
|
||||
self.status = _extract_redirect_to_status(options, response_status)
|
||||
self.location = _compute_redirect_to_location(options)
|
||||
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
|
||||
|
||||
logger.info("Redirected to #{location}") if logger && logger.info?
|
||||
end
|
||||
|
||||
private
|
||||
def _extract_redirect_to_status(options, response_status)
|
||||
status = if options.is_a?(Hash) && options.key?(:status)
|
||||
Rack::Utils.status_code(options.delete(:status))
|
||||
elsif response_status.key?(:status)
|
||||
Rack::Utils.status_code(response_status[:status])
|
||||
else
|
||||
302
|
||||
end
|
||||
end
|
||||
|
||||
def _compute_redirect_to_location(options)
|
||||
case options
|
||||
# The scheme name consist of a letter followed by any combination of
|
||||
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
|
||||
# characters; and is terminated by a colon (":").
|
||||
when %r{^\w[\w\d+.-]*:.*}
|
||||
options
|
||||
when String
|
||||
request.protocol + request.host_with_port + options
|
||||
when :back
|
||||
raise RedirectBackError unless refer = request.headers["Referer"]
|
||||
refer
|
||||
else
|
||||
url_for(options)
|
||||
end.gsub(/[\r\n]/, '')
|
||||
end
|
||||
end
|
||||
end
|
@ -1,22 +0,0 @@
|
||||
module ActionController
|
||||
class RedirectBackError < AbstractController::Error #:nodoc:
|
||||
DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].'
|
||||
|
||||
def initialize(message = nil)
|
||||
super(message || DEFAULT_MESSAGE)
|
||||
end
|
||||
end
|
||||
|
||||
module Redirector
|
||||
extend ActiveSupport::Concern
|
||||
include AbstractController::Logger
|
||||
|
||||
def redirect_to(url, status) #:doc:
|
||||
raise AbstractController::DoubleRenderError if response_body
|
||||
logger.info("Redirected to #{url}") if logger && logger.info?
|
||||
self.status = status
|
||||
self.location = url.gsub(/[\r\n]/, '')
|
||||
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(url)}\">redirected</a>.</body></html>"
|
||||
end
|
||||
end
|
||||
end
|
@ -1,103 +0,0 @@
|
||||
module ActionController
|
||||
module RenderOptions
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
extlib_inheritable_accessor :_renderers
|
||||
self._renderers = []
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def _write_render_options
|
||||
renderers = _renderers.map do |r|
|
||||
<<-RUBY_EVAL
|
||||
if options.key?(:#{r})
|
||||
_process_options(options)
|
||||
return render_#{r}(options[:#{r}], options)
|
||||
end
|
||||
RUBY_EVAL
|
||||
end
|
||||
|
||||
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||
def _handle_render_options(options)
|
||||
#{renderers.join}
|
||||
end
|
||||
RUBY_EVAL
|
||||
end
|
||||
|
||||
def _add_render_option(name)
|
||||
_renderers << name
|
||||
_write_render_options
|
||||
end
|
||||
end
|
||||
|
||||
def render_to_body(options)
|
||||
_handle_render_options(options) || super
|
||||
end
|
||||
end
|
||||
|
||||
module RenderOption #:nodoc:
|
||||
def self.extended(base)
|
||||
base.extend ActiveSupport::Concern
|
||||
base.send :include, ::ActionController::RenderOptions
|
||||
|
||||
def base.register_renderer(name)
|
||||
included { _add_render_option(name) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module RenderOptions
|
||||
module Json
|
||||
extend RenderOption
|
||||
register_renderer :json
|
||||
|
||||
def render_json(json, options)
|
||||
json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str)
|
||||
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
|
||||
self.content_type ||= Mime::JSON
|
||||
self.response_body = json
|
||||
end
|
||||
end
|
||||
|
||||
module Js
|
||||
extend RenderOption
|
||||
register_renderer :js
|
||||
|
||||
def render_js(js, options)
|
||||
self.content_type ||= Mime::JS
|
||||
self.response_body = js.respond_to?(:to_js) ? js.to_js : js
|
||||
end
|
||||
end
|
||||
|
||||
module Xml
|
||||
extend RenderOption
|
||||
register_renderer :xml
|
||||
|
||||
def render_xml(xml, options)
|
||||
self.content_type ||= Mime::XML
|
||||
self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml
|
||||
end
|
||||
end
|
||||
|
||||
module RJS
|
||||
extend RenderOption
|
||||
register_renderer :update
|
||||
|
||||
def render_update(proc, options)
|
||||
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc)
|
||||
self.content_type = Mime::JS
|
||||
self.response_body = generator.to_s
|
||||
end
|
||||
end
|
||||
|
||||
module All
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include ActionController::RenderOptions::Json
|
||||
include ActionController::RenderOptions::Js
|
||||
include ActionController::RenderOptions::Xml
|
||||
include ActionController::RenderOptions::RJS
|
||||
end
|
||||
end
|
||||
end
|
91
actionpack/lib/action_controller/metal/renderers.rb
Normal file
91
actionpack/lib/action_controller/metal/renderers.rb
Normal file
@ -0,0 +1,91 @@
|
||||
module ActionController
|
||||
def self.add_renderer(key, &block)
|
||||
Renderers.add(key, &block)
|
||||
end
|
||||
|
||||
module Renderers
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
extlib_inheritable_accessor :_renderers
|
||||
self._renderers = {}
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def _write_render_options
|
||||
renderers = _renderers.map do |name, value|
|
||||
<<-RUBY_EVAL
|
||||
if options.key?(:#{name})
|
||||
_process_options(options)
|
||||
return _render_option_#{name}(options[:#{name}], options)
|
||||
end
|
||||
RUBY_EVAL
|
||||
end
|
||||
|
||||
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||
def _handle_render_options(options)
|
||||
#{renderers.join}
|
||||
end
|
||||
RUBY_EVAL
|
||||
end
|
||||
|
||||
def use_renderers(*args)
|
||||
args.each do |key|
|
||||
_renderers[key] = RENDERERS[key]
|
||||
end
|
||||
_write_render_options
|
||||
end
|
||||
alias use_renderer use_renderers
|
||||
end
|
||||
|
||||
def render_to_body(options)
|
||||
_handle_render_options(options) || super
|
||||
end
|
||||
|
||||
RENDERERS = {}
|
||||
def self.add(key, &block)
|
||||
define_method("_render_option_#{key}", &block)
|
||||
RENDERERS[key] = block
|
||||
All._write_render_options
|
||||
end
|
||||
|
||||
module All
|
||||
extend ActiveSupport::Concern
|
||||
include Renderers
|
||||
|
||||
INCLUDED = []
|
||||
included do
|
||||
self._renderers = RENDERERS
|
||||
_write_render_options
|
||||
INCLUDED << self
|
||||
end
|
||||
|
||||
def self._write_render_options
|
||||
INCLUDED.each(&:_write_render_options)
|
||||
end
|
||||
end
|
||||
|
||||
add :json do |json, options|
|
||||
json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str)
|
||||
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
|
||||
self.content_type ||= Mime::JSON
|
||||
self.response_body = json
|
||||
end
|
||||
|
||||
add :js do |js, options|
|
||||
self.content_type ||= Mime::JS
|
||||
self.response_body = js.respond_to?(:to_js) ? js.to_js : js
|
||||
end
|
||||
|
||||
add :xml do |xml, options|
|
||||
self.content_type ||= Mime::XML
|
||||
self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml
|
||||
end
|
||||
|
||||
add :update do |proc, options|
|
||||
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc)
|
||||
self.content_type = Mime::JS
|
||||
self.response_body = generator.to_s
|
||||
end
|
||||
end
|
||||
end
|
@ -1,9 +1,9 @@
|
||||
module ActionController
|
||||
module RenderingController
|
||||
module Rendering
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include AbstractController::RenderingController
|
||||
include AbstractController::Rendering
|
||||
include AbstractController::LocalizedCache
|
||||
end
|
||||
|
||||
@ -20,12 +20,6 @@ def render(options)
|
||||
|
||||
def render_to_body(options)
|
||||
_process_options(options)
|
||||
|
||||
if options.key?(:partial)
|
||||
options[:partial] = action_name if options[:partial] == true
|
||||
options[:_details] = {:formats => formats}
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
@ -43,6 +37,12 @@ def _determine_template(options)
|
||||
super
|
||||
end
|
||||
|
||||
def _render_partial(options)
|
||||
options[:partial] = action_name if options[:partial] == true
|
||||
options[:_details] = {:formats => formats}
|
||||
super
|
||||
end
|
||||
|
||||
def format_for_text
|
||||
formats.first
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user