Rails 2.1 RC1
Updated Instiki to Rails 2.1 RC1 (aka 2.0.991).
This commit is contained in:
parent
14afed5893
commit
5292899c9a
971 changed files with 46318 additions and 17450 deletions
|
@ -1,5 +1,3 @@
|
|||
require 'application'
|
||||
|
||||
class AdminController < ApplicationController
|
||||
|
||||
layout 'default'
|
||||
|
|
140
config/boot.rb
140
config/boot.rb
|
@ -1,45 +1,109 @@
|
|||
# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
|
||||
# Don't change this file!
|
||||
# Configure your app in config/environment.rb and config/environments/*.rb
|
||||
|
||||
unless defined?(RAILS_ROOT)
|
||||
root_path = File.join(File.dirname(__FILE__), '..')
|
||||
RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
|
||||
|
||||
unless RUBY_PLATFORM =~ /mswin32/
|
||||
require 'pathname'
|
||||
root_path = Pathname.new(root_path).cleanpath(true).to_s
|
||||
end
|
||||
|
||||
RAILS_ROOT = root_path
|
||||
end
|
||||
|
||||
unless defined?(Rails::Initializer)
|
||||
if File.directory?("#{RAILS_ROOT}/vendor/rails")
|
||||
require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
|
||||
else
|
||||
require 'rubygems'
|
||||
|
||||
environment_without_comments = IO.readlines(File.dirname(__FILE__) + '/environment.rb').reject { |l| l =~ /^#/ }.join
|
||||
environment_without_comments =~ /[^#]RAILS_GEM_VERSION = '([\d.]+)'/
|
||||
rails_gem_version = $1
|
||||
|
||||
if version = defined?(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : rails_gem_version
|
||||
# Asking for 1.1.6 will give you 1.1.6.5206, if available -- makes it easier to use beta gems
|
||||
rails_gem = Gem.cache.search('rails', "~>#{version}.0").sort_by { |g| g.version.version }.last
|
||||
|
||||
if rails_gem
|
||||
gem "rails", "=#{rails_gem.version.version}"
|
||||
require rails_gem.full_gem_path + '/lib/initializer'
|
||||
else
|
||||
STDERR.puts %(Cannot find gem for Rails ~>#{version}.0:
|
||||
Install the missing gem with 'gem install -v=#{version} rails', or
|
||||
change environment.rb to define RAILS_GEM_VERSION with your desired version.
|
||||
)
|
||||
exit 1
|
||||
module Rails
|
||||
class << self
|
||||
def boot!
|
||||
unless booted?
|
||||
preinitialize
|
||||
pick_boot.run
|
||||
end
|
||||
else
|
||||
gem "rails"
|
||||
require 'initializer'
|
||||
end
|
||||
|
||||
def booted?
|
||||
defined? Rails::Initializer
|
||||
end
|
||||
|
||||
def pick_boot
|
||||
(vendor_rails? ? VendorBoot : GemBoot).new
|
||||
end
|
||||
|
||||
def vendor_rails?
|
||||
File.exist?("#{RAILS_ROOT}/vendor/rails")
|
||||
end
|
||||
|
||||
def preinitialize
|
||||
load(preinitializer_path) if File.exist?(preinitializer_path)
|
||||
end
|
||||
|
||||
def preinitializer_path
|
||||
"#{RAILS_ROOT}/config/preinitializer.rb"
|
||||
end
|
||||
end
|
||||
|
||||
Rails::Initializer.run(:set_load_path)
|
||||
class Boot
|
||||
def run
|
||||
load_initializer
|
||||
Rails::Initializer.run(:set_load_path)
|
||||
end
|
||||
end
|
||||
|
||||
class VendorBoot < Boot
|
||||
def load_initializer
|
||||
require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
|
||||
Rails::Initializer.run(:install_gem_spec_stubs)
|
||||
end
|
||||
end
|
||||
|
||||
class GemBoot < Boot
|
||||
def load_initializer
|
||||
self.class.load_rubygems
|
||||
load_rails_gem
|
||||
require 'initializer'
|
||||
end
|
||||
|
||||
def load_rails_gem
|
||||
if version = self.class.gem_version
|
||||
gem 'rails', version
|
||||
else
|
||||
gem 'rails'
|
||||
end
|
||||
rescue Gem::LoadError => load_error
|
||||
$stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
|
||||
exit 1
|
||||
end
|
||||
|
||||
class << self
|
||||
def rubygems_version
|
||||
Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
|
||||
end
|
||||
|
||||
def gem_version
|
||||
if defined? RAILS_GEM_VERSION
|
||||
RAILS_GEM_VERSION
|
||||
elsif ENV.include?('RAILS_GEM_VERSION')
|
||||
ENV['RAILS_GEM_VERSION']
|
||||
else
|
||||
parse_gem_version(read_environment_rb)
|
||||
end
|
||||
end
|
||||
|
||||
def load_rubygems
|
||||
require 'rubygems'
|
||||
|
||||
unless rubygems_version >= '0.9.4'
|
||||
$stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
|
||||
exit 1
|
||||
end
|
||||
|
||||
rescue LoadError
|
||||
$stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
|
||||
exit 1
|
||||
end
|
||||
|
||||
def parse_gem_version(text)
|
||||
$1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
|
||||
end
|
||||
|
||||
private
|
||||
def read_environment_rb
|
||||
File.read("#{RAILS_ROOT}/config/environment.rb")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# All that for this:
|
||||
Rails.boot!
|
||||
|
|
|
@ -30,6 +30,9 @@ Rails::Initializer.run do |config|
|
|||
#####
|
||||
}
|
||||
|
||||
# Don't do file system STAT calls to check to see if the templates have changed.
|
||||
#config.action_view.cache_template_loading = true
|
||||
|
||||
# Skip frameworks you're not going to use
|
||||
config.frameworks -= [ :action_web_service, :action_mailer ]
|
||||
|
||||
|
@ -39,7 +42,7 @@ Rails::Initializer.run do |config|
|
|||
|
||||
# Enable page/fragment caching by setting a file-based store
|
||||
# (remember to create the caching directory and make it readable to the application)
|
||||
config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache"
|
||||
config.action_controller.cache_store = :file_store, "#{RAILS_ROOT}/cache"
|
||||
|
||||
# Activate observers that should always be running
|
||||
config.active_record.observers = :page_observer
|
||||
|
|
|
@ -10,6 +10,7 @@ class AdminControllerTest < Test::Unit::TestCase
|
|||
fixtures :webs, :pages, :revisions, :system, :wiki_references
|
||||
|
||||
def setup
|
||||
require 'action_controller/test_process'
|
||||
@controller = AdminController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
|
|
|
@ -3,11 +3,9 @@ ENV['RAILS_ENV'] = 'test'
|
|||
# Expand the path to environment so that Ruby does not load it multiple times
|
||||
# File.expand_path can be removed if Ruby 1.9 is in use.
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../config/environment')
|
||||
require 'application'
|
||||
|
||||
require 'test/unit'
|
||||
require 'active_record/fixtures'
|
||||
require 'action_controller/test_process'
|
||||
require 'wiki_content'
|
||||
require 'url_generator'
|
||||
require 'digest/sha1'
|
||||
|
|
11
vendor/rails/actionmailer/CHANGELOG
vendored
11
vendor/rails/actionmailer/CHANGELOG
vendored
|
@ -1,3 +1,14 @@
|
|||
*2.1.0 RC1 (May 11th, 2008)*
|
||||
|
||||
* Fixed that a return-path header would be ignored #7572 [joost]
|
||||
|
||||
* Less verbose mail logging: just recipients for :info log level; the whole email for :debug only. #8000 [iaddict, Tarmo Tänav]
|
||||
|
||||
* Updated TMail to version 1.2.1 [raasdnil]
|
||||
|
||||
* Fixed that you don't have to call super in ActionMailer::TestCase#setup #10406 [jamesgolick]
|
||||
|
||||
|
||||
*2.0.2* (December 16th, 2007)
|
||||
|
||||
* Included in Rails 2.0.2
|
||||
|
|
2
vendor/rails/actionmailer/MIT-LICENSE
vendored
2
vendor/rails/actionmailer/MIT-LICENSE
vendored
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2004-2007 David Heinemeier Hansson
|
||||
Copyright (c) 2004-2008 David Heinemeier Hansson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
|
5
vendor/rails/actionmailer/Rakefile
vendored
5
vendor/rails/actionmailer/Rakefile
vendored
|
@ -4,7 +4,7 @@ require 'rake/testtask'
|
|||
require 'rake/rdoctask'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
require 'rake/contrib/sshpublisher'
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
|
||||
|
||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||
|
@ -55,7 +55,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.rubyforge_project = "actionmailer"
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
|
||||
s.add_dependency('actionpack', '= 2.0.2' + PKG_BUILD)
|
||||
s.add_dependency('actionpack', '= 2.0.991' + PKG_BUILD)
|
||||
|
||||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
|
@ -87,6 +87,7 @@ end
|
|||
desc "Publish the release files to RubyForge."
|
||||
task :release => [ :package ] do
|
||||
require 'rubyforge'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
|
||||
packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#--
|
||||
# Copyright (c) 2004-2007 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2008 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
|
|
@ -16,7 +16,7 @@ module ActionMailer
|
|||
define_method(name) do |*parameters|
|
||||
raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1
|
||||
if parameters.empty?
|
||||
if instance_variables.include?(ivar)
|
||||
if instance_variable_names.include?(ivar)
|
||||
instance_variable_get(ivar)
|
||||
end
|
||||
else
|
||||
|
|
|
@ -40,10 +40,14 @@ module ActionMailer #:nodoc:
|
|||
# * <tt>content_type</tt> - Specify the content type of the message. Defaults to <tt>text/plain</tt>.
|
||||
# * <tt>headers</tt> - Specify additional headers to be set for the message, e.g. <tt>headers 'X-Mail-Count' => 107370</tt>.
|
||||
#
|
||||
# When a <tt>headers 'return-path'</tt> is specified, that value will be used as the 'envelope from'
|
||||
# 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
|
||||
# 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.
|
||||
#
|
||||
|
@ -69,21 +73,36 @@ module ActionMailer #:nodoc:
|
|||
# <%= truncate(note.body, 25) %>
|
||||
#
|
||||
#
|
||||
# = Generating URLs for mailer views
|
||||
# = Generating URLs
|
||||
#
|
||||
# If your view includes URLs from the application, you need to use url_for in the mailing method instead of the view.
|
||||
# Unlike controllers from Action Pack, the mailer instance doesn't have any context about the incoming request. That's
|
||||
# why you need to jump this little hoop and supply all the details needed for the URL. Example:
|
||||
# URLs can be generated in mailer views using <tt>url_for</tt> or named routes.
|
||||
# Unlike controllers from Action Pack, the mailer instance doesn't have any context about the incoming request,
|
||||
# so you'll need to provide all of the details needed to generate a URL.
|
||||
#
|
||||
# def signup_notification(recipient)
|
||||
# recipients recipient.email_address_with_name
|
||||
# from "system@example.com"
|
||||
# subject "New account information"
|
||||
# body :account => recipient,
|
||||
# :home_page => url_for(:host => "example.com", :controller => "welcome", :action => "greeting")
|
||||
# end
|
||||
# When using <tt>url_for</tt> you'll need to provide the <tt>:host</tt>, <tt>:controller</tt>, and <tt>:action</tt>:
|
||||
#
|
||||
# You can now access @home_page in the template and get http://example.com/welcome/greeting.
|
||||
# <%= url_for(:host => "example.com", :controller => "welcome", :action => "greeting") %>
|
||||
#
|
||||
# When using named routes you only need to supply the <tt>:host</tt>:
|
||||
#
|
||||
# <%= users_url(:host => "example.com") %>
|
||||
#
|
||||
# You will want to avoid using the <tt>name_of_route_path</tt> form of named routes because it doesn't make sense to
|
||||
# generate relative URLs in email messages.
|
||||
#
|
||||
# It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt> option in
|
||||
# the <tt>ActionMailer::Base.default_url_options</tt> hash as follows:
|
||||
#
|
||||
# ActionMailer::Base.default_url_options[:host] = "example.com"
|
||||
#
|
||||
# This can also be set as a configuration option in <tt>config/environment.rb</tt>:
|
||||
#
|
||||
# config.action_mailer.default_url_options = { :host => "example.com" }
|
||||
#
|
||||
# If you do decide to set a default <tt>:host</tt> for your mailers you will want to use the
|
||||
# <tt>:only_path => false</tt> option when using <tt>url_for</tt>. This will ensure that absolute URLs are generated because
|
||||
# the <tt>url_for</tt> view helper will, by default, generate relative URLs when a <tt>:host</tt> option isn't
|
||||
# explicitly provided.
|
||||
#
|
||||
# = Sending mail
|
||||
#
|
||||
|
@ -179,31 +198,32 @@ module ActionMailer #:nodoc:
|
|||
#
|
||||
# These options are specified on the class level, like <tt>ActionMailer::Base.template_root = "/my/templates"</tt>
|
||||
#
|
||||
# * <tt>template_root</tt> - template root determines the base from which template references will be made.
|
||||
# * <tt>template_root</tt> - Determines the base from which template references will be made.
|
||||
#
|
||||
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
|
||||
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
|
||||
#
|
||||
# * <tt>smtp_settings</tt> - Allows detailed configuration for :smtp delivery method:
|
||||
# * <tt>:address</tt> Allows you to use a remote mail server. Just change it from its default "localhost" setting.
|
||||
# * <tt>:port</tt> On the off chance that your mail server doesn't run on port 25, you can change it.
|
||||
# * <tt>:domain</tt> If you need to specify a HELO domain, you can do it here.
|
||||
# * <tt>:user_name</tt> If your mail server requires authentication, set the username in this setting.
|
||||
# * <tt>:password</tt> If your mail server requires authentication, set the password in this setting.
|
||||
# * <tt>:authentication</tt> If your mail server requires authentication, you need to specify the authentication type here.
|
||||
# This is a symbol and one of :plain, :login, :cram_md5
|
||||
# * <tt>smtp_settings</tt> - Allows detailed configuration for <tt>:smtp</tt> delivery method:
|
||||
# * <tt>:address</tt> - Allows you to use a remote mail server. Just change it from its default "localhost" setting.
|
||||
# * <tt>:port</tt> - On the off chance that your mail server doesn't run on port 25, you can change it.
|
||||
# * <tt>:domain</tt> - If you need to specify a HELO domain, you can do it here.
|
||||
# * <tt>:user_name</tt> - If your mail server requires authentication, set the username in this setting.
|
||||
# * <tt>:password</tt> - If your mail server requires authentication, set the password in this setting.
|
||||
# * <tt>:authentication</tt> - If your mail server requires authentication, you need to specify the authentication type here.
|
||||
# This is a symbol and one of <tt>:plain</tt>, <tt>:login</tt>, <tt>:cram_md5</tt>
|
||||
#
|
||||
# * <tt>sendmail_settings</tt> - Allows you to override options for the :sendmail delivery method
|
||||
# * <tt>:location</tt> The location of the sendmail executable, defaults to "/usr/sbin/sendmail"
|
||||
# * <tt>:arguments</tt> The command line arguments
|
||||
# * <tt>raise_delivery_errors</tt> - whether or not errors should be raised if the email fails to be delivered.
|
||||
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method
|
||||
# * <tt>:location</tt> - The location of the sendmail executable, defaults to "/usr/sbin/sendmail"
|
||||
# * <tt>:arguments</tt> - The command line arguments
|
||||
#
|
||||
# * <tt>delivery_method</tt> - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test.
|
||||
# * <tt>raise_delivery_errors</tt> - Whether or not errors should be raised if the email fails to be delivered.
|
||||
#
|
||||
# * <tt>perform_deliveries</tt> - Determines whether deliver_* methods are actually carried out. By default they are,
|
||||
# * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, and <tt>:test</tt>.
|
||||
#
|
||||
# * <tt>perform_deliveries</tt> - Determines whether <tt>deliver_*</tt> methods are actually carried out. By default they are,
|
||||
# but this can be turned off to help functional testing.
|
||||
#
|
||||
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful
|
||||
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with <tt>delivery_method :test</tt>. Most useful
|
||||
# for unit and functional testing.
|
||||
#
|
||||
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
|
||||
|
@ -387,12 +407,17 @@ module ActionMailer #:nodoc:
|
|||
# templating language other than rhtml or rxml are supported.
|
||||
# To use this, include in your template-language plugin's init
|
||||
# code or on a per-application basis, this can be invoked from
|
||||
# config/environment.rb:
|
||||
# <tt>config/environment.rb</tt>:
|
||||
#
|
||||
# ActionMailer::Base.register_template_extension('haml')
|
||||
def register_template_extension(extension)
|
||||
template_extensions << extension
|
||||
end
|
||||
|
||||
def template_root=(root)
|
||||
write_inheritable_attribute(:template_root, root)
|
||||
ActionView::TemplateFinder.process_view_paths(root)
|
||||
end
|
||||
end
|
||||
|
||||
# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
|
||||
|
@ -463,7 +488,10 @@ module ActionMailer #:nodoc:
|
|||
# no alternate has been given as the parameter, this will fail.
|
||||
def deliver!(mail = @mail)
|
||||
raise "no mail object available for delivery!" unless mail
|
||||
logger.info "Sent mail:\n #{mail.encoded}" unless logger.nil?
|
||||
unless logger.nil?
|
||||
logger.info "Sent mail to #{Array(recipients).join(', ')}"
|
||||
logger.debug "\n#{mail.encoded}"
|
||||
end
|
||||
|
||||
begin
|
||||
__send__("perform_delivery_#{delivery_method}", mail) if perform_deliveries
|
||||
|
@ -582,15 +610,18 @@ module ActionMailer #:nodoc:
|
|||
def perform_delivery_smtp(mail)
|
||||
destinations = mail.destinations
|
||||
mail.ready_to_send
|
||||
sender = mail['return-path'] || mail.from
|
||||
|
||||
Net::SMTP.start(smtp_settings[:address], smtp_settings[:port], smtp_settings[:domain],
|
||||
smtp_settings[:user_name], smtp_settings[:password], smtp_settings[:authentication]) do |smtp|
|
||||
smtp.sendmail(mail.encoded, mail.from, destinations)
|
||||
smtp.sendmail(mail.encoded, sender, destinations)
|
||||
end
|
||||
end
|
||||
|
||||
def perform_delivery_sendmail(mail)
|
||||
IO.popen("#{sendmail_settings[:location]} #{sendmail_settings[:arguments]}","w+") do |sm|
|
||||
sendmail_args = sendmail_settings[:arguments]
|
||||
sendmail_args += " -f \"#{mail['return-path']}\"" if mail['return-path']
|
||||
IO.popen("#{sendmail_settings[:location]} #{sendmail_args}","w+") do |sm|
|
||||
sm.print(mail.encoded.gsub(/\r/, ''))
|
||||
sm.flush
|
||||
end
|
||||
|
|
|
@ -93,9 +93,9 @@ module ActionMailer
|
|||
begin
|
||||
child.master_helper_module = Module.new
|
||||
child.master_helper_module.send! :include, master_helper_module
|
||||
child.helper child.name.underscore
|
||||
child.helper child.name.to_s.underscore
|
||||
rescue MissingSourceFile => e
|
||||
raise unless e.is_missing?("helpers/#{child.name.underscore}_helper")
|
||||
raise unless e.is_missing?("helpers/#{child.name.to_s.underscore}_helper")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,6 +24,8 @@ module ActionMailer
|
|||
|
||||
# Quote the given text if it contains any "illegal" characters
|
||||
def quote_if_necessary(text, charset)
|
||||
text = text.dup.force_encoding(Encoding::ASCII_8BIT) if text.respond_to?(:force_encoding)
|
||||
|
||||
(text =~ CHARS_NEEDING_QUOTING) ?
|
||||
quoted_printable(text, charset) :
|
||||
text
|
||||
|
|
|
@ -8,11 +8,13 @@ module ActionMailer
|
|||
"test case definition"
|
||||
end
|
||||
end
|
||||
# New Test Super class for forward compatibility.
|
||||
# To override
|
||||
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
include ActionMailer::Quoting
|
||||
|
||||
setup :initialize_test_deliveries
|
||||
setup :set_expected_mail
|
||||
|
||||
class << self
|
||||
def tests(mailer)
|
||||
write_inheritable_attribute(:mailer_class, mailer)
|
||||
|
@ -33,15 +35,18 @@ module ActionMailer
|
|||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
ActionMailer::Base.delivery_method = :test
|
||||
ActionMailer::Base.perform_deliveries = true
|
||||
ActionMailer::Base.deliveries = []
|
||||
protected
|
||||
def initialize_test_deliveries
|
||||
ActionMailer::Base.delivery_method = :test
|
||||
ActionMailer::Base.perform_deliveries = true
|
||||
ActionMailer::Base.deliveries = []
|
||||
end
|
||||
|
||||
@expected = TMail::Mail.new
|
||||
@expected.set_content_type "text", "plain", { "charset" => charset }
|
||||
@expected.mime_version = '1.0'
|
||||
end
|
||||
def set_expected_mail
|
||||
@expected = TMail::Mail.new
|
||||
@expected.set_content_type "text", "plain", { "charset" => charset }
|
||||
@expected.mime_version = '1.0'
|
||||
end
|
||||
|
||||
private
|
||||
def charset
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
require 'rubygems'
|
||||
|
||||
begin
|
||||
gem 'tmail', '~> 1.1.0'
|
||||
gem 'tmail', '~> 1.2.2'
|
||||
rescue Gem::LoadError
|
||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/tmail-1.1.0"
|
||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/tmail-1.2.2"
|
||||
end
|
||||
|
||||
begin
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
#
|
||||
# lib/tmail/Makefile
|
||||
#
|
||||
|
||||
debug:
|
||||
rm -f parser.rb
|
||||
make parser.rb DEBUG=true
|
||||
|
||||
parser.rb: parser.y
|
||||
if [ "$(DEBUG)" = true ]; then \
|
||||
racc -v -g -o$@ parser.y ;\
|
||||
else \
|
||||
racc -E -o$@ parser.y ;\
|
||||
fi
|
||||
|
||||
clean:
|
||||
rm -f parser.rb parser.output
|
||||
|
||||
distclean: clean
|
|
@ -1,245 +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
|
||||
|
||||
include TextUtils
|
||||
|
||||
def Address.parse( str )
|
||||
Parser.parse :ADDRESS, str
|
||||
end
|
||||
|
||||
def address_group?
|
||||
false
|
||||
end
|
||||
|
||||
def initialize( local, domain )
|
||||
if domain
|
||||
domain.each do |s|
|
||||
raise SyntaxError, 'empty word in domain' if s.empty?
|
||||
end
|
||||
end
|
||||
|
||||
@local = local
|
||||
@domain = domain
|
||||
@name = nil
|
||||
@routes = []
|
||||
end
|
||||
|
||||
attr_reader :name
|
||||
|
||||
def name=( str )
|
||||
@name = str
|
||||
@name = nil if str and str.empty?
|
||||
end
|
||||
|
||||
alias phrase name
|
||||
alias phrase= name=
|
||||
|
||||
attr_reader :routes
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{address()}>"
|
||||
end
|
||||
|
||||
def local
|
||||
return nil unless @local
|
||||
return '""' if @local.size == 1 and @local[0].empty?
|
||||
@local.map {|i| quote_atom(i) }.join('.')
|
||||
end
|
||||
|
||||
def domain
|
||||
return nil unless @domain
|
||||
join_domain(@domain)
|
||||
end
|
||||
|
||||
def spec
|
||||
s = self.local
|
||||
d = self.domain
|
||||
if s and d
|
||||
s + '@' + d
|
||||
else
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
alias address spec
|
||||
|
||||
def ==( other )
|
||||
other.respond_to? :spec and self.spec == other.spec
|
||||
end
|
||||
|
||||
alias eql? ==
|
||||
|
||||
def hash
|
||||
@local.hash ^ @domain.hash
|
||||
end
|
||||
|
||||
def dup
|
||||
obj = self.class.new(@local.dup, @domain.dup)
|
||||
obj.name = @name.dup if @name
|
||||
obj.routes.replace @routes
|
||||
obj
|
||||
end
|
||||
|
||||
include StrategyInterface
|
||||
|
||||
def accept( strategy, dummy1 = nil, dummy2 = nil )
|
||||
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,552 +0,0 @@
|
|||
#
|
||||
# facade.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.
|
||||
#++
|
||||
|
||||
require 'tmail/utils'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class Mail
|
||||
|
||||
def header_string( name, default = nil )
|
||||
h = @header[name.downcase] or return default
|
||||
h.to_s
|
||||
end
|
||||
|
||||
###
|
||||
### attributes
|
||||
###
|
||||
|
||||
include TextUtils
|
||||
|
||||
def set_string_array_attr( key, strs )
|
||||
strs.flatten!
|
||||
if strs.empty?
|
||||
@header.delete key.downcase
|
||||
else
|
||||
store key, strs.join(', ')
|
||||
end
|
||||
strs
|
||||
end
|
||||
private :set_string_array_attr
|
||||
|
||||
def set_string_attr( key, str )
|
||||
if str
|
||||
store key, str
|
||||
else
|
||||
@header.delete key.downcase
|
||||
end
|
||||
str
|
||||
end
|
||||
private :set_string_attr
|
||||
|
||||
def set_addrfield( name, arg )
|
||||
if arg
|
||||
h = HeaderField.internal_new(name, @config)
|
||||
h.addrs.replace [arg].flatten
|
||||
@header[name] = h
|
||||
else
|
||||
@header.delete name
|
||||
end
|
||||
arg
|
||||
end
|
||||
private :set_addrfield
|
||||
|
||||
def addrs2specs( addrs )
|
||||
return nil unless addrs
|
||||
list = addrs.map {|addr|
|
||||
if addr.address_group?
|
||||
then addr.map {|a| a.spec }
|
||||
else addr.spec
|
||||
end
|
||||
}.flatten
|
||||
return nil if list.empty?
|
||||
list
|
||||
end
|
||||
private :addrs2specs
|
||||
|
||||
|
||||
#
|
||||
# date time
|
||||
#
|
||||
|
||||
def date( default = nil )
|
||||
if h = @header['date']
|
||||
h.date
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def date=( time )
|
||||
if time
|
||||
store 'Date', time2str(time)
|
||||
else
|
||||
@header.delete 'date'
|
||||
end
|
||||
time
|
||||
end
|
||||
|
||||
def strftime( fmt, default = nil )
|
||||
if t = date
|
||||
t.strftime(fmt)
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# destination
|
||||
#
|
||||
|
||||
def to_addrs( default = nil )
|
||||
if h = @header['to']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def cc_addrs( default = nil )
|
||||
if h = @header['cc']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def bcc_addrs( default = nil )
|
||||
if h = @header['bcc']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def to_addrs=( arg )
|
||||
set_addrfield 'to', arg
|
||||
end
|
||||
|
||||
def cc_addrs=( arg )
|
||||
set_addrfield 'cc', arg
|
||||
end
|
||||
|
||||
def bcc_addrs=( arg )
|
||||
set_addrfield 'bcc', arg
|
||||
end
|
||||
|
||||
def to( default = nil )
|
||||
addrs2specs(to_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def cc( default = nil )
|
||||
addrs2specs(cc_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def bcc( default = nil )
|
||||
addrs2specs(bcc_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def to=( *strs )
|
||||
set_string_array_attr 'To', strs
|
||||
end
|
||||
|
||||
def cc=( *strs )
|
||||
set_string_array_attr 'Cc', strs
|
||||
end
|
||||
|
||||
def bcc=( *strs )
|
||||
set_string_array_attr 'Bcc', strs
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# originator
|
||||
#
|
||||
|
||||
def from_addrs( default = nil )
|
||||
if h = @header['from']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def from_addrs=( arg )
|
||||
set_addrfield 'from', arg
|
||||
end
|
||||
|
||||
def from( default = nil )
|
||||
addrs2specs(from_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def from=( *strs )
|
||||
set_string_array_attr 'From', strs
|
||||
end
|
||||
|
||||
def friendly_from( default = nil )
|
||||
h = @header['from']
|
||||
a, = h.addrs
|
||||
return default unless a
|
||||
return a.phrase if a.phrase
|
||||
return h.comments.join(' ') unless h.comments.empty?
|
||||
a.spec
|
||||
end
|
||||
|
||||
|
||||
def reply_to_addrs( default = nil )
|
||||
if h = @header['reply-to']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def reply_to_addrs=( arg )
|
||||
set_addrfield 'reply-to', arg
|
||||
end
|
||||
|
||||
def reply_to( default = nil )
|
||||
addrs2specs(reply_to_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def reply_to=( *strs )
|
||||
set_string_array_attr 'Reply-To', strs
|
||||
end
|
||||
|
||||
|
||||
def sender_addr( default = nil )
|
||||
f = @header['sender'] or return default
|
||||
f.addr or return default
|
||||
end
|
||||
|
||||
def sender_addr=( addr )
|
||||
if addr
|
||||
h = HeaderField.internal_new('sender', @config)
|
||||
h.addr = addr
|
||||
@header['sender'] = h
|
||||
else
|
||||
@header.delete 'sender'
|
||||
end
|
||||
addr
|
||||
end
|
||||
|
||||
def sender( default )
|
||||
f = @header['sender'] or return default
|
||||
a = f.addr or return default
|
||||
a.spec
|
||||
end
|
||||
|
||||
def sender=( str )
|
||||
set_string_attr 'Sender', str
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# subject
|
||||
#
|
||||
|
||||
def subject( default = nil )
|
||||
if h = @header['subject']
|
||||
h.body
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
alias quoted_subject subject
|
||||
|
||||
def subject=( str )
|
||||
set_string_attr 'Subject', str
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# identity & threading
|
||||
#
|
||||
|
||||
def message_id( default = nil )
|
||||
if h = @header['message-id']
|
||||
h.id || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def message_id=( str )
|
||||
set_string_attr 'Message-Id', str
|
||||
end
|
||||
|
||||
def in_reply_to( default = nil )
|
||||
if h = @header['in-reply-to']
|
||||
h.ids
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def in_reply_to=( *idstrs )
|
||||
set_string_array_attr 'In-Reply-To', idstrs
|
||||
end
|
||||
|
||||
def references( default = nil )
|
||||
if h = @header['references']
|
||||
h.refs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def references=( *strs )
|
||||
set_string_array_attr 'References', strs
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# MIME headers
|
||||
#
|
||||
|
||||
def mime_version( default = nil )
|
||||
if h = @header['mime-version']
|
||||
h.version || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def mime_version=( m, opt = nil )
|
||||
if opt
|
||||
if h = @header['mime-version']
|
||||
h.major = m
|
||||
h.minor = opt
|
||||
else
|
||||
store 'Mime-Version', "#{m}.#{opt}"
|
||||
end
|
||||
else
|
||||
store 'Mime-Version', m
|
||||
end
|
||||
m
|
||||
end
|
||||
|
||||
|
||||
def content_type( default = nil )
|
||||
if h = @header['content-type']
|
||||
h.content_type || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def main_type( default = nil )
|
||||
if h = @header['content-type']
|
||||
h.main_type || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def sub_type( default = nil )
|
||||
if h = @header['content-type']
|
||||
h.sub_type || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def set_content_type( str, sub = nil, param = nil )
|
||||
if sub
|
||||
main, sub = str, sub
|
||||
else
|
||||
main, sub = str.split(%r</>, 2)
|
||||
raise ArgumentError, "sub type missing: #{str.inspect}" unless sub
|
||||
end
|
||||
if h = @header['content-type']
|
||||
h.main_type = main
|
||||
h.sub_type = sub
|
||||
h.params.clear
|
||||
else
|
||||
store 'Content-Type', "#{main}/#{sub}"
|
||||
end
|
||||
@header['content-type'].params.replace param if param
|
||||
|
||||
str
|
||||
end
|
||||
|
||||
alias content_type= set_content_type
|
||||
|
||||
def type_param( name, default = nil )
|
||||
if h = @header['content-type']
|
||||
h[name] || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def charset( default = nil )
|
||||
if h = @header['content-type']
|
||||
h['charset'] or default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def charset=( str )
|
||||
if str
|
||||
if h = @header[ 'content-type' ]
|
||||
h['charset'] = str
|
||||
else
|
||||
store 'Content-Type', "text/plain; charset=#{str}"
|
||||
end
|
||||
end
|
||||
str
|
||||
end
|
||||
|
||||
|
||||
def transfer_encoding( default = nil )
|
||||
if h = @header['content-transfer-encoding']
|
||||
h.encoding || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def transfer_encoding=( str )
|
||||
set_string_attr 'Content-Transfer-Encoding', str
|
||||
end
|
||||
|
||||
alias encoding transfer_encoding
|
||||
alias encoding= transfer_encoding=
|
||||
alias content_transfer_encoding transfer_encoding
|
||||
alias content_transfer_encoding= transfer_encoding=
|
||||
|
||||
|
||||
def disposition( default = nil )
|
||||
if h = @header['content-disposition']
|
||||
h.disposition || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
alias content_disposition disposition
|
||||
|
||||
def set_disposition( str, params = nil )
|
||||
if h = @header['content-disposition']
|
||||
h.disposition = str
|
||||
h.params.clear
|
||||
else
|
||||
store('Content-Disposition', str)
|
||||
h = @header['content-disposition']
|
||||
end
|
||||
h.params.replace params if params
|
||||
end
|
||||
|
||||
alias disposition= set_disposition
|
||||
alias set_content_disposition set_disposition
|
||||
alias content_disposition= set_disposition
|
||||
|
||||
def disposition_param( name, default = nil )
|
||||
if h = @header['content-disposition']
|
||||
h[name] || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
###
|
||||
### utils
|
||||
###
|
||||
|
||||
def create_reply
|
||||
mail = TMail::Mail.parse('')
|
||||
mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '')
|
||||
mail.to_addrs = reply_addresses([])
|
||||
mail.in_reply_to = [message_id(nil)].compact
|
||||
mail.references = references([]) + [message_id(nil)].compact
|
||||
mail.mime_version = '1.0'
|
||||
mail
|
||||
end
|
||||
|
||||
|
||||
def base64_encode
|
||||
store 'Content-Transfer-Encoding', 'Base64'
|
||||
self.body = Base64.folding_encode(self.body)
|
||||
end
|
||||
|
||||
def base64_decode
|
||||
if /base64/i === self.transfer_encoding('')
|
||||
store 'Content-Transfer-Encoding', '8bit'
|
||||
self.body = Base64.decode(self.body, @config.strict_base64decode?)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def destinations( default = nil )
|
||||
ret = []
|
||||
%w( to cc bcc ).each do |nm|
|
||||
if h = @header[nm]
|
||||
h.addrs.each {|i| ret.push i.address }
|
||||
end
|
||||
end
|
||||
ret.empty? ? default : ret
|
||||
end
|
||||
|
||||
def each_destination( &block )
|
||||
destinations([]).each do |i|
|
||||
if Address === i
|
||||
yield i
|
||||
else
|
||||
i.each(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias each_dest each_destination
|
||||
|
||||
|
||||
def reply_addresses( default = nil )
|
||||
reply_to_addrs(nil) or from_addrs(nil) or default
|
||||
end
|
||||
|
||||
def error_reply_addresses( default = nil )
|
||||
if s = sender(nil)
|
||||
[s]
|
||||
else
|
||||
from_addrs(default)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def multipart?
|
||||
main_type('').downcase == 'multipart'
|
||||
end
|
||||
|
||||
end # class Mail
|
||||
|
||||
end # module TMail
|
|
@ -1,35 +0,0 @@
|
|||
#
|
||||
# info.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.
|
||||
#++
|
||||
|
||||
module TMail
|
||||
|
||||
Version = '0.10.7'
|
||||
Copyright = 'Copyright (c) 1998-2002 Minero Aoki'
|
||||
|
||||
end
|
|
@ -1,540 +0,0 @@
|
|||
=begin rdoc
|
||||
|
||||
= Facade.rb Provides an interface to the TMail object
|
||||
|
||||
=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/utils'
|
||||
|
||||
module TMail
|
||||
|
||||
class Mail
|
||||
|
||||
def header_string( name, default = nil )
|
||||
h = @header[name.downcase] or return default
|
||||
h.to_s
|
||||
end
|
||||
|
||||
###
|
||||
### attributes
|
||||
###
|
||||
|
||||
include TextUtils
|
||||
|
||||
def set_string_array_attr( key, strs )
|
||||
strs.flatten!
|
||||
if strs.empty?
|
||||
@header.delete key.downcase
|
||||
else
|
||||
store key, strs.join(', ')
|
||||
end
|
||||
strs
|
||||
end
|
||||
private :set_string_array_attr
|
||||
|
||||
def set_string_attr( key, str )
|
||||
if str
|
||||
store key, str
|
||||
else
|
||||
@header.delete key.downcase
|
||||
end
|
||||
str
|
||||
end
|
||||
private :set_string_attr
|
||||
|
||||
def set_addrfield( name, arg )
|
||||
if arg
|
||||
h = HeaderField.internal_new(name, @config)
|
||||
h.addrs.replace [arg].flatten
|
||||
@header[name] = h
|
||||
else
|
||||
@header.delete name
|
||||
end
|
||||
arg
|
||||
end
|
||||
private :set_addrfield
|
||||
|
||||
def addrs2specs( addrs )
|
||||
return nil unless addrs
|
||||
list = addrs.map {|addr|
|
||||
if addr.address_group?
|
||||
then addr.map {|a| a.spec }
|
||||
else addr.spec
|
||||
end
|
||||
}.flatten
|
||||
return nil if list.empty?
|
||||
list
|
||||
end
|
||||
private :addrs2specs
|
||||
|
||||
#
|
||||
# date time
|
||||
#
|
||||
|
||||
def date( default = nil )
|
||||
if h = @header['date']
|
||||
h.date
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def date=( time )
|
||||
if time
|
||||
store 'Date', time2str(time)
|
||||
else
|
||||
@header.delete 'date'
|
||||
end
|
||||
time
|
||||
end
|
||||
|
||||
def strftime( fmt, default = nil )
|
||||
if t = date
|
||||
t.strftime(fmt)
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# destination
|
||||
#
|
||||
|
||||
def to_addrs( default = nil )
|
||||
if h = @header['to']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def cc_addrs( default = nil )
|
||||
if h = @header['cc']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def bcc_addrs( default = nil )
|
||||
if h = @header['bcc']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def to_addrs=( arg )
|
||||
set_addrfield 'to', arg
|
||||
end
|
||||
|
||||
def cc_addrs=( arg )
|
||||
set_addrfield 'cc', arg
|
||||
end
|
||||
|
||||
def bcc_addrs=( arg )
|
||||
set_addrfield 'bcc', arg
|
||||
end
|
||||
|
||||
def to( default = nil )
|
||||
addrs2specs(to_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def cc( default = nil )
|
||||
addrs2specs(cc_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def bcc( default = nil )
|
||||
addrs2specs(bcc_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def to=( *strs )
|
||||
set_string_array_attr 'To', strs
|
||||
end
|
||||
|
||||
def cc=( *strs )
|
||||
set_string_array_attr 'Cc', strs
|
||||
end
|
||||
|
||||
def bcc=( *strs )
|
||||
set_string_array_attr 'Bcc', strs
|
||||
end
|
||||
|
||||
#
|
||||
# originator
|
||||
#
|
||||
|
||||
def from_addrs( default = nil )
|
||||
if h = @header['from']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def from_addrs=( arg )
|
||||
set_addrfield 'from', arg
|
||||
end
|
||||
|
||||
def from( default = nil )
|
||||
addrs2specs(from_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def from=( *strs )
|
||||
set_string_array_attr 'From', strs
|
||||
end
|
||||
|
||||
def friendly_from( default = nil )
|
||||
h = @header['from']
|
||||
a, = h.addrs
|
||||
return default unless a
|
||||
return a.phrase if a.phrase
|
||||
return h.comments.join(' ') unless h.comments.empty?
|
||||
a.spec
|
||||
end
|
||||
|
||||
|
||||
def reply_to_addrs( default = nil )
|
||||
if h = @header['reply-to']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def reply_to_addrs=( arg )
|
||||
set_addrfield 'reply-to', arg
|
||||
end
|
||||
|
||||
def reply_to( default = nil )
|
||||
addrs2specs(reply_to_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def reply_to=( *strs )
|
||||
set_string_array_attr 'Reply-To', strs
|
||||
end
|
||||
|
||||
|
||||
def sender_addr( default = nil )
|
||||
f = @header['sender'] or return default
|
||||
f.addr or return default
|
||||
end
|
||||
|
||||
def sender_addr=( addr )
|
||||
if addr
|
||||
h = HeaderField.internal_new('sender', @config)
|
||||
h.addr = addr
|
||||
@header['sender'] = h
|
||||
else
|
||||
@header.delete 'sender'
|
||||
end
|
||||
addr
|
||||
end
|
||||
|
||||
def sender( default )
|
||||
f = @header['sender'] or return default
|
||||
a = f.addr or return default
|
||||
a.spec
|
||||
end
|
||||
|
||||
def sender=( str )
|
||||
set_string_attr 'Sender', str
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# subject
|
||||
#
|
||||
|
||||
def subject( default = nil )
|
||||
if h = @header['subject']
|
||||
h.body
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
alias quoted_subject subject
|
||||
|
||||
def subject=( str )
|
||||
set_string_attr 'Subject', str
|
||||
end
|
||||
|
||||
#
|
||||
# identity & threading
|
||||
#
|
||||
|
||||
def message_id( default = nil )
|
||||
if h = @header['message-id']
|
||||
h.id || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def message_id=( str )
|
||||
set_string_attr 'Message-Id', str
|
||||
end
|
||||
|
||||
def in_reply_to( default = nil )
|
||||
if h = @header['in-reply-to']
|
||||
h.ids
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def in_reply_to=( *idstrs )
|
||||
set_string_array_attr 'In-Reply-To', idstrs
|
||||
end
|
||||
|
||||
def references( default = nil )
|
||||
if h = @header['references']
|
||||
h.refs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def references=( *strs )
|
||||
set_string_array_attr 'References', strs
|
||||
end
|
||||
|
||||
#
|
||||
# MIME headers
|
||||
#
|
||||
|
||||
def mime_version( default = nil )
|
||||
if h = @header['mime-version']
|
||||
h.version || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def mime_version=( m, opt = nil )
|
||||
if opt
|
||||
if h = @header['mime-version']
|
||||
h.major = m
|
||||
h.minor = opt
|
||||
else
|
||||
store 'Mime-Version', "#{m}.#{opt}"
|
||||
end
|
||||
else
|
||||
store 'Mime-Version', m
|
||||
end
|
||||
m
|
||||
end
|
||||
|
||||
def content_type( default = nil )
|
||||
if h = @header['content-type']
|
||||
h.content_type || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def main_type( default = nil )
|
||||
if h = @header['content-type']
|
||||
h.main_type || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def sub_type( default = nil )
|
||||
if h = @header['content-type']
|
||||
h.sub_type || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def set_content_type( str, sub = nil, param = nil )
|
||||
if sub
|
||||
main, sub = str, sub
|
||||
else
|
||||
main, sub = str.split(%r</>, 2)
|
||||
raise ArgumentError, "sub type missing: #{str.inspect}" unless sub
|
||||
end
|
||||
if h = @header['content-type']
|
||||
h.main_type = main
|
||||
h.sub_type = sub
|
||||
h.params.clear
|
||||
else
|
||||
store 'Content-Type', "#{main}/#{sub}"
|
||||
end
|
||||
@header['content-type'].params.replace param if param
|
||||
str
|
||||
end
|
||||
|
||||
alias content_type= set_content_type
|
||||
|
||||
def type_param( name, default = nil )
|
||||
if h = @header['content-type']
|
||||
h[name] || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def charset( default = nil )
|
||||
if h = @header['content-type']
|
||||
h['charset'] or default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def charset=( str )
|
||||
if str
|
||||
if h = @header[ 'content-type' ]
|
||||
h['charset'] = str
|
||||
else
|
||||
store 'Content-Type', "text/plain; charset=#{str}"
|
||||
end
|
||||
end
|
||||
str
|
||||
end
|
||||
|
||||
def transfer_encoding( default = nil )
|
||||
if h = @header['content-transfer-encoding']
|
||||
h.encoding || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def transfer_encoding=( str )
|
||||
set_string_attr 'Content-Transfer-Encoding', str
|
||||
end
|
||||
|
||||
alias encoding transfer_encoding
|
||||
alias encoding= transfer_encoding=
|
||||
alias content_transfer_encoding transfer_encoding
|
||||
alias content_transfer_encoding= transfer_encoding=
|
||||
|
||||
def disposition( default = nil )
|
||||
if h = @header['content-disposition']
|
||||
h.disposition || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
alias content_disposition disposition
|
||||
|
||||
def set_disposition( str, params = nil )
|
||||
if h = @header['content-disposition']
|
||||
h.disposition = str
|
||||
h.params.clear
|
||||
else
|
||||
store('Content-Disposition', str)
|
||||
h = @header['content-disposition']
|
||||
end
|
||||
h.params.replace params if params
|
||||
end
|
||||
|
||||
alias disposition= set_disposition
|
||||
alias set_content_disposition set_disposition
|
||||
alias content_disposition= set_disposition
|
||||
|
||||
def disposition_param( name, default = nil )
|
||||
if h = @header['content-disposition']
|
||||
h[name] || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
###
|
||||
### utils
|
||||
###
|
||||
|
||||
def create_reply
|
||||
mail = TMail::Mail.parse('')
|
||||
mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '')
|
||||
mail.to_addrs = reply_addresses([])
|
||||
mail.in_reply_to = [message_id(nil)].compact
|
||||
mail.references = references([]) + [message_id(nil)].compact
|
||||
mail.mime_version = '1.0'
|
||||
mail
|
||||
end
|
||||
|
||||
def base64_encode
|
||||
store 'Content-Transfer-Encoding', 'Base64'
|
||||
self.body = Base64.folding_encode(self.body)
|
||||
end
|
||||
|
||||
def base64_decode
|
||||
if /base64/i === self.transfer_encoding('')
|
||||
store 'Content-Transfer-Encoding', '8bit'
|
||||
self.body = Base64.decode(self.body, @config.strict_base64decode?)
|
||||
end
|
||||
end
|
||||
|
||||
def destinations( default = nil )
|
||||
ret = []
|
||||
%w( to cc bcc ).each do |nm|
|
||||
if h = @header[nm]
|
||||
h.addrs.each {|i| ret.push i.address }
|
||||
end
|
||||
end
|
||||
ret.empty? ? default : ret
|
||||
end
|
||||
|
||||
def each_destination( &block )
|
||||
destinations([]).each do |i|
|
||||
if Address === i
|
||||
yield i
|
||||
else
|
||||
i.each(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias each_dest each_destination
|
||||
|
||||
def reply_addresses( default = nil )
|
||||
reply_to_addrs(nil) or from_addrs(nil) or default
|
||||
end
|
||||
|
||||
def error_reply_addresses( default = nil )
|
||||
if s = sender(nil)
|
||||
[s]
|
||||
else
|
||||
from_addrs(default)
|
||||
end
|
||||
end
|
||||
|
||||
def multipart?
|
||||
main_type('').downcase == 'multipart'
|
||||
end
|
||||
|
||||
end # class Mail
|
||||
|
||||
end # module TMail
|
|
@ -1,381 +0,0 @@
|
|||
#
|
||||
# parser.y
|
||||
#
|
||||
# Copyright (c) 1998-2007 Minero Aoki
|
||||
#
|
||||
# This program is free software.
|
||||
# You can distribute/modify this program under the terms of
|
||||
# the GNU Lesser General Public License version 2.1.
|
||||
#
|
||||
|
||||
class TMail::Parser
|
||||
|
||||
options no_result_var
|
||||
|
||||
rule
|
||||
|
||||
content : DATETIME datetime { val[1] }
|
||||
| RECEIVED received { val[1] }
|
||||
| MADDRESS addrs_TOP { val[1] }
|
||||
| RETPATH retpath { val[1] }
|
||||
| KEYWORDS keys { val[1] }
|
||||
| ENCRYPTED enc { val[1] }
|
||||
| MIMEVERSION version { val[1] }
|
||||
| CTYPE ctype { val[1] }
|
||||
| CENCODING cencode { val[1] }
|
||||
| CDISPOSITION cdisp { val[1] }
|
||||
| ADDRESS addr_TOP { val[1] }
|
||||
| MAILBOX mbox { val[1] }
|
||||
|
||||
datetime : day DIGIT ATOM DIGIT hour zone
|
||||
# 0 1 2 3 4 5
|
||||
# date month year
|
||||
{
|
||||
t = Time.gm(val[3].to_i, val[2], val[1].to_i, 0, 0, 0)
|
||||
(t + val[4] - val[5]).localtime
|
||||
}
|
||||
|
||||
day : /* none */
|
||||
| ATOM ','
|
||||
|
||||
hour : DIGIT ':' DIGIT
|
||||
{
|
||||
(val[0].to_i * 60 * 60) +
|
||||
(val[2].to_i * 60)
|
||||
}
|
||||
| DIGIT ':' DIGIT ':' DIGIT
|
||||
{
|
||||
(val[0].to_i * 60 * 60) +
|
||||
(val[2].to_i * 60) +
|
||||
(val[4].to_i)
|
||||
}
|
||||
|
||||
zone : ATOM
|
||||
{
|
||||
timezone_string_to_unixtime(val[0])
|
||||
}
|
||||
|
||||
received : from by via with id for received_datetime
|
||||
{
|
||||
val
|
||||
}
|
||||
|
||||
from : /* none */
|
||||
| FROM received_domain
|
||||
{
|
||||
val[1]
|
||||
}
|
||||
|
||||
by : /* none */
|
||||
| BY received_domain
|
||||
{
|
||||
val[1]
|
||||
}
|
||||
|
||||
received_domain
|
||||
: domain
|
||||
{
|
||||
join_domain(val[0])
|
||||
}
|
||||
| domain '@' domain
|
||||
{
|
||||
join_domain(val[2])
|
||||
}
|
||||
| domain DOMLIT
|
||||
{
|
||||
join_domain(val[0])
|
||||
}
|
||||
|
||||
via : /* none */
|
||||
| VIA ATOM
|
||||
{
|
||||
val[1]
|
||||
}
|
||||
|
||||
with : /* none */
|
||||
{
|
||||
[]
|
||||
}
|
||||
| with WITH ATOM
|
||||
{
|
||||
val[0].push val[2]
|
||||
val[0]
|
||||
}
|
||||
|
||||
id : /* none */
|
||||
| ID msgid
|
||||
{
|
||||
val[1]
|
||||
}
|
||||
| ID ATOM
|
||||
{
|
||||
val[1]
|
||||
}
|
||||
|
||||
for : /* none */
|
||||
| FOR received_addrspec
|
||||
{
|
||||
val[1]
|
||||
}
|
||||
|
||||
received_addrspec
|
||||
: routeaddr
|
||||
{
|
||||
val[0].spec
|
||||
}
|
||||
| spec
|
||||
{
|
||||
val[0].spec
|
||||
}
|
||||
|
||||
received_datetime
|
||||
: /* none */
|
||||
| ';' datetime
|
||||
{
|
||||
val[1]
|
||||
}
|
||||
|
||||
addrs_TOP : addrs
|
||||
| group_bare
|
||||
| addrs commas group_bare
|
||||
|
||||
addr_TOP : mbox
|
||||
| group
|
||||
| group_bare
|
||||
|
||||
retpath : addrs_TOP
|
||||
| '<' '>' { [ Address.new(nil, nil) ] }
|
||||
|
||||
addrs : addr
|
||||
{
|
||||
val
|
||||
}
|
||||
| addrs commas addr
|
||||
{
|
||||
val[0].push val[2]
|
||||
val[0]
|
||||
}
|
||||
|
||||
addr : mbox
|
||||
| group
|
||||
|
||||
mboxes : mbox
|
||||
{
|
||||
val
|
||||
}
|
||||
| mboxes commas mbox
|
||||
{
|
||||
val[0].push val[2]
|
||||
val[0]
|
||||
}
|
||||
|
||||
mbox : spec
|
||||
| routeaddr
|
||||
| addr_phrase routeaddr
|
||||
{
|
||||
val[1].phrase = Decoder.decode(val[0])
|
||||
val[1]
|
||||
}
|
||||
|
||||
group : group_bare ';'
|
||||
|
||||
group_bare: addr_phrase ':' mboxes
|
||||
{
|
||||
AddressGroup.new(val[0], val[2])
|
||||
}
|
||||
| addr_phrase ':' { AddressGroup.new(val[0], []) }
|
||||
|
||||
addr_phrase
|
||||
: local_head { val[0].join('.') }
|
||||
| addr_phrase local_head { val[0] << ' ' << val[1].join('.') }
|
||||
|
||||
routeaddr : '<' routes spec '>'
|
||||
{
|
||||
val[2].routes.replace val[1]
|
||||
val[2]
|
||||
}
|
||||
| '<' spec '>'
|
||||
{
|
||||
val[1]
|
||||
}
|
||||
|
||||
routes : at_domains ':'
|
||||
|
||||
at_domains: '@' domain { [ val[1].join('.') ] }
|
||||
| at_domains ',' '@' domain { val[0].push val[3].join('.'); val[0] }
|
||||
|
||||
spec : local '@' domain { Address.new( val[0], val[2] ) }
|
||||
| local { Address.new( val[0], nil ) }
|
||||
|
||||
local: local_head
|
||||
| local_head '.' { val[0].push ''; val[0] }
|
||||
|
||||
local_head: word
|
||||
{ val }
|
||||
| local_head dots word
|
||||
{
|
||||
val[1].times do
|
||||
val[0].push ''
|
||||
end
|
||||
val[0].push val[2]
|
||||
val[0]
|
||||
}
|
||||
|
||||
domain : domword
|
||||
{ val }
|
||||
| domain dots domword
|
||||
{
|
||||
val[1].times do
|
||||
val[0].push ''
|
||||
end
|
||||
val[0].push val[2]
|
||||
val[0]
|
||||
}
|
||||
|
||||
dots : '.' { 0 }
|
||||
| '.' '.' { 1 }
|
||||
|
||||
word : atom
|
||||
| QUOTED
|
||||
| DIGIT
|
||||
|
||||
domword : atom
|
||||
| DOMLIT
|
||||
| DIGIT
|
||||
|
||||
commas : ','
|
||||
| commas ','
|
||||
|
||||
msgid : '<' spec '>'
|
||||
{
|
||||
val[1] = val[1].spec
|
||||
val.join('')
|
||||
}
|
||||
|
||||
keys : phrase { val }
|
||||
| keys ',' phrase { val[0].push val[2]; val[0] }
|
||||
|
||||
phrase : word
|
||||
| phrase word { val[0] << ' ' << val[1] }
|
||||
|
||||
enc : word
|
||||
{
|
||||
val.push nil
|
||||
val
|
||||
}
|
||||
| word word
|
||||
{
|
||||
val
|
||||
}
|
||||
|
||||
version : DIGIT '.' DIGIT
|
||||
{
|
||||
[ val[0].to_i, val[2].to_i ]
|
||||
}
|
||||
|
||||
ctype : TOKEN '/' TOKEN params opt_semicolon
|
||||
{
|
||||
[ val[0].downcase, val[2].downcase, decode_params(val[3]) ]
|
||||
}
|
||||
| TOKEN params opt_semicolon
|
||||
{
|
||||
[ val[0].downcase, nil, decode_params(val[1]) ]
|
||||
}
|
||||
|
||||
params : /* none */
|
||||
{
|
||||
{}
|
||||
}
|
||||
| params ';' TOKEN '=' QUOTED
|
||||
{
|
||||
val[0][ val[2].downcase ] = ('"' + val[4].to_s + '"')
|
||||
val[0]
|
||||
}
|
||||
| params ';' TOKEN '=' TOKEN
|
||||
{
|
||||
val[0][ val[2].downcase ] = val[4]
|
||||
val[0]
|
||||
}
|
||||
|
||||
cencode : TOKEN
|
||||
{
|
||||
val[0].downcase
|
||||
}
|
||||
|
||||
cdisp : TOKEN params opt_semicolon
|
||||
{
|
||||
[ val[0].downcase, decode_params(val[1]) ]
|
||||
}
|
||||
|
||||
opt_semicolon
|
||||
:
|
||||
| ';'
|
||||
|
||||
atom : ATOM
|
||||
| FROM
|
||||
| BY
|
||||
| VIA
|
||||
| WITH
|
||||
| ID
|
||||
| FOR
|
||||
|
||||
end
|
||||
|
||||
|
||||
---- header
|
||||
#
|
||||
# parser.rb
|
||||
#
|
||||
# Copyright (c) 1998-2007 Minero Aoki
|
||||
#
|
||||
# This program is free software.
|
||||
# You can distribute/modify this program under the terms of
|
||||
# the GNU Lesser General Public License version 2.1.
|
||||
#
|
||||
|
||||
require 'tmail/scanner'
|
||||
require 'tmail/utils'
|
||||
|
||||
---- inner
|
||||
|
||||
include TextUtils
|
||||
|
||||
def self.parse( ident, str, cmt = nil )
|
||||
new.parse(ident, str, cmt)
|
||||
end
|
||||
|
||||
MAILP_DEBUG = false
|
||||
|
||||
def initialize
|
||||
self.debug = MAILP_DEBUG
|
||||
end
|
||||
|
||||
def debug=( flag )
|
||||
@yydebug = flag && Racc_debug_parser
|
||||
@scanner_debug = flag
|
||||
end
|
||||
|
||||
def debug
|
||||
@yydebug
|
||||
end
|
||||
|
||||
def parse( ident, str, comments = nil )
|
||||
@scanner = Scanner.new(str, ident, comments)
|
||||
@scanner.debug = @scanner_debug
|
||||
@first = [ident, ident]
|
||||
result = yyparse(self, :parse_in)
|
||||
comments.map! {|c| to_kcode(c) } if comments
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_in( &block )
|
||||
yield @first
|
||||
@scanner.scan(&block)
|
||||
end
|
||||
|
||||
def on_error( t, val, vstack )
|
||||
raise SyntaxError, "parse error on token #{racc_token2str t}"
|
||||
end
|
||||
|
|
@ -1 +0,0 @@
|
|||
require 'tmail'
|
1
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail.rb
vendored
Executable file → Normal file
1
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail.rb
vendored
Executable file → Normal file
|
@ -2,3 +2,4 @@ require 'tmail/version'
|
|||
require 'tmail/mail'
|
||||
require 'tmail/mailbox'
|
||||
require 'tmail/core_extensions'
|
||||
require 'tmail/net'
|
426
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/address.rb
vendored
Normal file
426
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/address.rb
vendored
Normal file
|
@ -0,0 +1,426 @@
|
|||
=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 themseleves. 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 becomming <@@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,6 +1,6 @@
|
|||
=begin rdoc
|
||||
|
||||
= Attachment handling class
|
||||
= Attachment handling file
|
||||
|
||||
=end
|
||||
|
||||
|
@ -17,8 +17,7 @@ module TMail
|
|||
end
|
||||
|
||||
def attachment?(part)
|
||||
(part['content-disposition'] && part['content-disposition'].disposition == "attachment") ||
|
||||
part.header['content-type'].main_type != "text"
|
||||
part.disposition_is_attachment? || part.content_type_is_text?
|
||||
end
|
||||
|
||||
def attachments
|
14
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/base64.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/base64.rb
vendored
Executable file → Normal file
14
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/base64.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/base64.rb
vendored
Executable file → Normal file
|
@ -1,9 +1,4 @@
|
|||
# = TITLE:
|
||||
#
|
||||
# Base64
|
||||
#
|
||||
# = COPYRIGHT:
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
|
@ -27,10 +22,9 @@
|
|||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
|
||||
#
|
||||
#++
|
||||
#:stopdoc:
|
||||
module TMail
|
||||
|
||||
module Base64
|
||||
|
||||
module_function
|
||||
|
@ -48,5 +42,5 @@ module TMail
|
|||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
|
@ -1,17 +1,18 @@
|
|||
#:stopdoc:
|
||||
unless Enumerable.method_defined?(:map)
|
||||
module Enumerable
|
||||
module Enumerable #:nodoc:
|
||||
alias map collect
|
||||
end
|
||||
end
|
||||
|
||||
unless Enumerable.method_defined?(:select)
|
||||
module Enumerable
|
||||
module Enumerable #:nodoc:
|
||||
alias select find_all
|
||||
end
|
||||
end
|
||||
|
||||
unless Enumerable.method_defined?(:reject)
|
||||
module Enumerable
|
||||
module Enumerable #:nodoc:
|
||||
def reject
|
||||
result = []
|
||||
each do |i|
|
||||
|
@ -23,7 +24,7 @@ unless Enumerable.method_defined?(:reject)
|
|||
end
|
||||
|
||||
unless Enumerable.method_defined?(:sort_by)
|
||||
module Enumerable
|
||||
module Enumerable #:nodoc:
|
||||
def sort_by
|
||||
map {|i| [yield(i), i] }.sort.map {|val, i| i }
|
||||
end
|
||||
|
@ -31,9 +32,10 @@ unless Enumerable.method_defined?(:sort_by)
|
|||
end
|
||||
|
||||
unless File.respond_to?(:read)
|
||||
def File.read(fname)
|
||||
def File.read(fname) #:nodoc:
|
||||
File.open(fname) {|f|
|
||||
return f.read
|
||||
}
|
||||
end
|
||||
end
|
||||
#:startdoc:
|
8
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/config.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/config.rb
vendored
Executable file → Normal file
8
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/config.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/config.rb
vendored
Executable file → Normal file
|
@ -1,8 +1,3 @@
|
|||
=begin rdoc
|
||||
|
||||
= Configuration Class
|
||||
|
||||
=end
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
|
@ -28,7 +23,7 @@
|
|||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
#:stopdoc:
|
||||
module TMail
|
||||
|
||||
class Config
|
||||
|
@ -69,3 +64,4 @@ module TMail
|
|||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
|
@ -1,14 +1,9 @@
|
|||
=begin rdoc
|
||||
|
||||
= Ruby on Rails Core Extensions
|
||||
|
||||
provides .blank?
|
||||
|
||||
=end
|
||||
unless Object.respond_to?(:blank?) #:nodoc:
|
||||
# Check first to see if we are in a Rails environment, no need to
|
||||
# define these methods if we are
|
||||
#: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.
|
||||
#
|
||||
|
@ -27,41 +22,42 @@ unless Object.respond_to?(:blank?) #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
class NilClass #:nodoc:
|
||||
class NilClass
|
||||
def blank?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class FalseClass #:nodoc:
|
||||
class FalseClass
|
||||
def blank?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class TrueClass #:nodoc:
|
||||
class TrueClass
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class Array #:nodoc:
|
||||
class Array
|
||||
alias_method :blank?, :empty?
|
||||
end
|
||||
|
||||
class Hash #:nodoc:
|
||||
class Hash
|
||||
alias_method :blank?, :empty?
|
||||
end
|
||||
|
||||
class String #:nodoc:
|
||||
class String
|
||||
def blank?
|
||||
empty? || strip.empty?
|
||||
end
|
||||
end
|
||||
|
||||
class Numeric #:nodoc:
|
||||
class Numeric
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
#:startdoc:
|
158
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/encode.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/encode.rb
vendored
Executable file → Normal file
158
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/encode.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/encode.rb
vendored
Executable file → Normal file
|
@ -1,42 +1,46 @@
|
|||
=begin rdoc
|
||||
|
||||
= Text Encoding class
|
||||
|
||||
=end
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
# = COPYRIGHT:
|
||||
#
|
||||
# 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:
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
# 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 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.
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
# 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.rb'
|
||||
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 )
|
||||
|
@ -53,10 +57,34 @@ module TMail
|
|||
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
|
||||
|
@ -65,7 +93,7 @@ module TMail
|
|||
|
||||
alias to_s decoded
|
||||
|
||||
def accept_strategy( klass, eol, charset, dest = nil )
|
||||
def accept_strategy( klass, eol, charset, dest = nil ) #:nodoc:
|
||||
dest ||= ''
|
||||
accept klass.new( create_dest(dest), charset, eol )
|
||||
dest
|
||||
|
@ -73,6 +101,7 @@ module TMail
|
|||
|
||||
end
|
||||
|
||||
#:stopdoc:
|
||||
|
||||
###
|
||||
### MIME B encoding decoder
|
||||
|
@ -91,8 +120,8 @@ module TMail
|
|||
}
|
||||
|
||||
def self.decode( str, encoding = nil )
|
||||
encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j')
|
||||
opt = '-m' + encoding
|
||||
encoding ||= (OUTPUT_ENCODING[TMail.KCODE] || 'j')
|
||||
opt = '-mS' + encoding
|
||||
str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
|
||||
end
|
||||
|
||||
|
@ -182,7 +211,8 @@ module TMail
|
|||
end
|
||||
|
||||
SPACER = "\t"
|
||||
MAX_LINE_LEN = 70
|
||||
MAX_LINE_LEN = 78
|
||||
RFC_2822_MAX_LENGTH = 998
|
||||
|
||||
OPTIONS = {
|
||||
'EUC' => '-Ej -m0',
|
||||
|
@ -193,8 +223,9 @@ module TMail
|
|||
|
||||
def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil )
|
||||
@f = StrategyInterface.create_dest(dest)
|
||||
@opt = OPTIONS[$KCODE]
|
||||
@opt = OPTIONS[TMail.KCODE]
|
||||
@eol = eol
|
||||
@folded = false
|
||||
@preserve_quotes = true
|
||||
reset
|
||||
end
|
||||
|
@ -367,11 +398,16 @@ module TMail
|
|||
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]
|
||||
when a then add_text strs[i]
|
||||
when s then add_lwsp strs[i]
|
||||
else
|
||||
raise "TMail FATAL: unknown flag: #{t.chr}"
|
||||
end
|
||||
|
@ -451,31 +487,75 @@ module TMail
|
|||
# puts '---- lwsp -------------------------------------'
|
||||
# puts "+ #{lwsp.inspect}"
|
||||
fold if restsize() <= 0
|
||||
flush
|
||||
flush(@folded)
|
||||
@lwsp = lwsp
|
||||
end
|
||||
|
||||
def flush
|
||||
def flush(folded = false)
|
||||
# puts '---- flush ----'
|
||||
# puts "spc >>>#{@lwsp.inspect}<<<"
|
||||
# puts "txt >>>#{@text.inspect}<<<"
|
||||
@f << @lwsp << @text
|
||||
@curlen += (@lwsp.size + @text.size)
|
||||
if folded
|
||||
@curlen = 0
|
||||
else
|
||||
@curlen += (@lwsp.size + @text.size)
|
||||
end
|
||||
@text = ''
|
||||
@lwsp = ''
|
||||
end
|
||||
|
||||
def fold
|
||||
# puts '---- fold ----'
|
||||
@f << @eol
|
||||
unless @f.string =~ /^.*?:$/
|
||||
@f << @eol
|
||||
@lwsp = SPACER
|
||||
else
|
||||
fold_header
|
||||
@folded = true
|
||||
end
|
||||
@curlen = 0
|
||||
@lwsp = SPACER
|
||||
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
|
||||
|
||||
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
|
53
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/header.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/header.rb
vendored
Executable file → Normal file
53
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/header.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/header.rb
vendored
Executable file → Normal file
|
@ -1,11 +1,3 @@
|
|||
=begin rdoc
|
||||
|
||||
= Header handling class
|
||||
|
||||
=end
|
||||
# RFC #822 ftp://ftp.isi.edu/in-notes/rfc822.txt
|
||||
#
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
|
@ -38,9 +30,10 @@ 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
|
||||
|
@ -54,8 +47,40 @@ module TMail
|
|||
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 emial)
|
||||
#
|
||||
# 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 )
|
||||
re = Regep.new('\A(' + Regexp.quote(name) + '):', 'i')
|
||||
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|
|
||||
|
@ -66,7 +91,7 @@ module TMail
|
|||
end
|
||||
end
|
||||
}
|
||||
new(name, str, Config.to_config(conf))
|
||||
new(name, str, Config.to_config(conf)) if str
|
||||
end
|
||||
|
||||
def internal_new( name, conf )
|
||||
|
@ -182,7 +207,11 @@ module TMail
|
|||
|
||||
def comments
|
||||
ensure_parsed
|
||||
@comments
|
||||
if @comments[0]
|
||||
[Decoder.decode(@comments[0])]
|
||||
else
|
||||
@comments
|
||||
end
|
||||
end
|
||||
|
||||
private
|
9
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/index.rb
vendored
Normal file
9
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/index.rb
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
#: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:
|
1125
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/interface.rb
vendored
Normal file
1125
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/interface.rb
vendored
Normal file
File diff suppressed because it is too large
Load diff
2
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/loader.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/loader.rb
vendored
Executable file → Normal file
2
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/loader.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/loader.rb
vendored
Executable file → Normal file
|
@ -1 +1,3 @@
|
|||
#:stopdoc:
|
||||
require 'tmail/mailbox'
|
||||
#:startdoc:
|
130
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mail.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/mail.rb
vendored
Executable file → Normal file
130
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mail.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/mail.rb
vendored
Executable file → Normal file
|
@ -29,6 +29,8 @@
|
|||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
|
||||
|
||||
require 'tmail/interface'
|
||||
require 'tmail/encode'
|
||||
require 'tmail/header'
|
||||
|
@ -41,9 +43,58 @@ 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
|
||||
|
@ -51,13 +102,30 @@ module TMail
|
|||
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 )
|
||||
def initialize( port = nil, conf = DEFAULT_CONFIG ) #:nodoc:
|
||||
@port = port || StringPort.new
|
||||
@config = Config.to_config(conf)
|
||||
|
||||
|
@ -73,6 +141,12 @@ module TMail
|
|||
}
|
||||
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
|
||||
|
@ -162,6 +236,14 @@ module TMail
|
|||
@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
|
||||
|
@ -172,6 +254,19 @@ module TMail
|
|||
|
||||
alias fetch []
|
||||
|
||||
# Allows you to set or delete TMail header objects at will.
|
||||
# Eamples:
|
||||
# @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 actualy 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
|
||||
|
||||
|
@ -204,6 +299,13 @@ module TMail
|
|||
|
||||
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 }
|
||||
|
@ -350,10 +452,12 @@ module TMail
|
|||
end
|
||||
|
||||
def quoted_body
|
||||
parse_body
|
||||
@body_port.ropen {|f|
|
||||
return f.read
|
||||
}
|
||||
body_port.ropen {|f| return f.read }
|
||||
end
|
||||
|
||||
def quoted_body= str
|
||||
body_port.wopen { |f| f.write str }
|
||||
str
|
||||
end
|
||||
|
||||
def body=( str )
|
||||
|
@ -375,8 +479,8 @@ module TMail
|
|||
str
|
||||
end
|
||||
|
||||
alias preamble body
|
||||
alias preamble= body=
|
||||
alias preamble quoted_body
|
||||
alias preamble= quoted_body=
|
||||
|
||||
def epilogue
|
||||
parse_body
|
||||
|
@ -398,6 +502,18 @@ module TMail
|
|||
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 )
|
86
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mailbox.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/mailbox.rb
vendored
Executable file → Normal file
86
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mailbox.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/mailbox.rb
vendored
Executable file → Normal file
|
@ -150,9 +150,78 @@ module TMail
|
|||
|
||||
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)
|
||||
f = File.open(fname, 'r+')
|
||||
f.flock File::LOCK_EX
|
||||
yield f
|
||||
ensure
|
||||
|
@ -161,15 +230,6 @@ module TMail
|
|||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
alias newobj new
|
||||
end
|
||||
|
||||
def UNIXMbox.new( fname, tmpdir = nil, readonly = false )
|
||||
tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp'
|
||||
newobj(fname, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false)
|
||||
end
|
||||
|
||||
def UNIXMbox.static_new( fname, dir, readonly = false )
|
||||
newobj(fname, dir, readonly, true)
|
||||
end
|
||||
|
@ -213,13 +273,13 @@ module TMail
|
|||
fromaddr(), TextUtils.time2str(File.mtime(port.filename))
|
||||
end
|
||||
|
||||
def UNIXMbox.fromaddr
|
||||
def UNIXMbox.fromaddr(port)
|
||||
h = HeaderField.new_from_port(port, 'Return-Path') ||
|
||||
HeaderField.new_from_port(port, 'From') or return 'nobody'
|
||||
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
|
||||
private_class_method :fromaddr
|
||||
|
||||
def close
|
||||
return if @closed
|
6
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/main.rb
vendored
Normal file
6
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/main.rb
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
#:stopdoc:
|
||||
require 'tmail/version'
|
||||
require 'tmail/mail'
|
||||
require 'tmail/mailbox'
|
||||
require 'tmail/core_extensions'
|
||||
#:startdoc:
|
2
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mbox.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/mbox.rb
vendored
Executable file → Normal file
2
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mbox.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/mbox.rb
vendored
Executable file → Normal file
|
@ -1 +1,3 @@
|
|||
#:stopdoc:
|
||||
require 'tmail/mailbox'
|
||||
#:startdoc:
|
62
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/net.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/net.rb
vendored
Executable file → Normal file
62
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/net.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/net.rb
vendored
Executable file → Normal file
|
@ -1,8 +1,3 @@
|
|||
=begin rdoc
|
||||
|
||||
= Net provides SMTP wrapping
|
||||
|
||||
=end
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
|
@ -29,8 +24,9 @@
|
|||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
#:stopdoc:
|
||||
require 'nkf'
|
||||
|
||||
#:startdoc:
|
||||
|
||||
module TMail
|
||||
|
||||
|
@ -129,44 +125,9 @@ module TMail
|
|||
end
|
||||
end
|
||||
|
||||
def create_empty_mail
|
||||
self.class.new(StringPort.new(''), @config)
|
||||
end
|
||||
|
||||
def create_reply
|
||||
setup_reply create_empty_mail()
|
||||
end
|
||||
|
||||
def setup_reply( m )
|
||||
if tmp = reply_addresses(nil)
|
||||
m.to_addrs = tmp
|
||||
end
|
||||
|
||||
mid = message_id(nil)
|
||||
tmp = references(nil) || []
|
||||
tmp.push mid if mid
|
||||
m.in_reply_to = [mid] if mid
|
||||
m.references = tmp unless tmp.empty?
|
||||
m.subject = 'Re: ' + subject('').sub(/\A(?:\s*re:)+/i, '')
|
||||
|
||||
m
|
||||
end
|
||||
|
||||
def create_forward
|
||||
setup_forward create_empty_mail()
|
||||
end
|
||||
|
||||
def setup_forward( mail )
|
||||
m = Mail.new(StringPort.new(''))
|
||||
m.body = decoded
|
||||
m.set_content_type 'message', 'rfc822'
|
||||
m.encoding = encoding('7bit')
|
||||
mail.parts.push m
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
#:stopdoc:
|
||||
class DeleteFields
|
||||
|
||||
NOSEND_FIELDS = %w(
|
||||
|
@ -190,8 +151,9 @@ module TMail
|
|||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
|
||||
#:stopdoc:
|
||||
class AddMessageId
|
||||
|
||||
def initialize( fqdn = nil )
|
||||
|
@ -205,8 +167,9 @@ module TMail
|
|||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
|
||||
#:stopdoc:
|
||||
class AddDate
|
||||
|
||||
def exec( mail )
|
||||
|
@ -214,8 +177,9 @@ module TMail
|
|||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
|
||||
#:stopdoc:
|
||||
class MimeEncodeAuto
|
||||
|
||||
def initialize( s = nil, m = nil )
|
||||
|
@ -233,8 +197,9 @@ module TMail
|
|||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
|
||||
#:stopdoc:
|
||||
class MimeEncodeSingle
|
||||
|
||||
def exec( mail )
|
||||
|
@ -260,8 +225,9 @@ module TMail
|
|||
end
|
||||
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
|
||||
#:stopdoc:
|
||||
class MimeEncodeMulti
|
||||
|
||||
def exec( mail, top = true )
|
||||
|
@ -278,5 +244,5 @@ module TMail
|
|||
end
|
||||
|
||||
end
|
||||
|
||||
#:startdoc:
|
||||
end # module TMail
|
17
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/obsolete.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/obsolete.rb
vendored
Executable file → Normal file
17
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/obsolete.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/obsolete.rb
vendored
Executable file → Normal file
|
@ -2,6 +2,9 @@
|
|||
|
||||
= Obsolete methods that are depriciated
|
||||
|
||||
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>
|
||||
|
@ -28,10 +31,9 @@
|
|||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
#:stopdoc:
|
||||
module TMail #:nodoc:
|
||||
|
||||
module TMail
|
||||
|
||||
# mail.rb
|
||||
class Mail
|
||||
alias include? key?
|
||||
alias has_key? key?
|
||||
|
@ -51,8 +53,6 @@ module TMail
|
|||
alias has_value? value?
|
||||
end
|
||||
|
||||
|
||||
# facade.rb
|
||||
class Mail
|
||||
def from_addr( default = nil )
|
||||
addr, = from_addrs(nil)
|
||||
|
@ -83,8 +83,6 @@ module TMail
|
|||
alias each_dest each_destination
|
||||
end
|
||||
|
||||
|
||||
# address.rb
|
||||
class Address
|
||||
alias route routes
|
||||
alias addr spec
|
||||
|
@ -97,8 +95,6 @@ module TMail
|
|||
alias address= spec=
|
||||
end
|
||||
|
||||
|
||||
# mbox.rb
|
||||
class MhMailbox
|
||||
alias new_mail new_port
|
||||
alias each_mail each_port
|
||||
|
@ -115,8 +111,6 @@ module TMail
|
|||
alias each_newmail each_new_port
|
||||
end
|
||||
|
||||
|
||||
# utils.rb
|
||||
extend TextUtils
|
||||
|
||||
class << self
|
||||
|
@ -135,3 +129,4 @@ module TMail
|
|||
end
|
||||
|
||||
end # module TMail
|
||||
#:startdoc:
|
1
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/parser.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/parser.rb
vendored
Executable file → Normal file
1
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/parser.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/parser.rb
vendored
Executable file → Normal file
|
@ -1,3 +1,4 @@
|
|||
#:stopdoc:
|
||||
# DO NOT MODIFY!!!!
|
||||
# This file is automatically generated by racc 1.4.5
|
||||
# from racc grammer file "parser.y".
|
0
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/port.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/port.rb
vendored
Executable file → Normal file
0
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/port.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/port.rb
vendored
Executable file → Normal file
|
@ -116,27 +116,3 @@ module TMail
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
require 'test/unit'
|
||||
|
||||
class TC_Unquoter < Test::Unit::TestCase
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b
|
||||
end
|
||||
end
|
||||
end
|
58
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/require_arch.rb
vendored
Normal file
58
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/require_arch.rb
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
#: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:
|
18
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/scanner.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/scanner.rb
vendored
Executable file → Normal file
18
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/scanner.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/scanner.rb
vendored
Executable file → Normal file
|
@ -28,16 +28,22 @@
|
|||
# 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
|
||||
require 'tmail/scanner_r.rb'
|
||||
# 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, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT']
|
||||
require 'tmail/scanner_c.so'
|
||||
Scanner = Scanner_C
|
||||
raise LoadError, 'Turned off native extentions by user choice' if ENV['NORUBYEXT']
|
||||
require('tmail/tmailscanner') # c extension
|
||||
Scanner = TMailScanner
|
||||
rescue LoadError
|
||||
Scanner = Scanner_R
|
||||
require 'tmail/scanner_r'
|
||||
Scanner = TMailScanner
|
||||
end
|
||||
end
|
||||
#:stopdoc:
|
34
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/scanner_r.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/scanner_r.rb
vendored
Executable file → Normal file
34
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/scanner_r.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/scanner_r.rb
vendored
Executable file → Normal file
|
@ -1,4 +1,3 @@
|
|||
#
|
||||
# scanner_r.rb
|
||||
#
|
||||
#--
|
||||
|
@ -26,15 +25,14 @@
|
|||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
#:stopdoc:
|
||||
require 'tmail/config'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class Scanner_R
|
||||
class TMailScanner
|
||||
|
||||
Version = '0.10.7'
|
||||
Version = '1.2.3'
|
||||
Version.freeze
|
||||
|
||||
MIME_HEADERS = {
|
||||
|
@ -46,14 +44,13 @@ module TMail
|
|||
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])+'
|
||||
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
|
||||
|
@ -107,7 +104,7 @@ module TMail
|
|||
@received = (scantype == :RECEIVED)
|
||||
@is_mime_header = MIME_HEADERS[scantype]
|
||||
|
||||
atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE]
|
||||
atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[TMail.KCODE]
|
||||
@word_re = (MIME_HEADERS[scantype] ? token : atom)
|
||||
end
|
||||
|
||||
|
@ -147,34 +144,34 @@ module TMail
|
|||
|
||||
if s = readstr(@word_re)
|
||||
if @is_mime_header
|
||||
yield :TOKEN, s
|
||||
yield [:TOKEN, s]
|
||||
else
|
||||
# atom
|
||||
if /\A\d+\z/ === s
|
||||
yield :DIGIT, s
|
||||
yield [:DIGIT, s]
|
||||
elsif @received
|
||||
yield RECV_TOKEN[s.downcase] || :ATOM, s
|
||||
yield [RECV_TOKEN[s.downcase] || :ATOM, s]
|
||||
else
|
||||
yield :ATOM, s
|
||||
yield [:ATOM, s]
|
||||
end
|
||||
end
|
||||
|
||||
elsif skip(/\A"/)
|
||||
yield :QUOTED, scan_quoted_word()
|
||||
yield [:QUOTED, scan_quoted_word()]
|
||||
|
||||
elsif skip(/\A\[/)
|
||||
yield :DOMLIT, scan_domain_literal()
|
||||
yield [:DOMLIT, scan_domain_literal()]
|
||||
|
||||
elsif skip(/\A\(/)
|
||||
@comments.push scan_comment()
|
||||
|
||||
else
|
||||
c = readchar()
|
||||
yield c, c
|
||||
yield [c, c]
|
||||
end
|
||||
end
|
||||
|
||||
yield false, '$'
|
||||
yield [false, '$']
|
||||
end
|
||||
|
||||
def scan_quoted_word
|
||||
|
@ -261,3 +258,4 @@ module TMail
|
|||
end
|
||||
|
||||
end # module TMail
|
||||
#:startdoc:
|
1
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/stringio.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/stringio.rb
vendored
Executable file → Normal file
1
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/stringio.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/stringio.rb
vendored
Executable file → Normal file
|
@ -1,3 +1,4 @@
|
|||
# encoding: utf-8
|
||||
=begin rdoc
|
||||
|
||||
= String handling class
|
122
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/utils.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/utils.rb
vendored
Executable file → Normal file
122
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/utils.rb → vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.2/tmail/utils.rb
vendored
Executable file → Normal file
|
@ -1,8 +1,3 @@
|
|||
=begin rdoc
|
||||
|
||||
= General Purpose TMail Utilities
|
||||
|
||||
=end
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
|
@ -29,21 +24,73 @@
|
|||
# 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
|
||||
|
||||
def TMail.random_tag
|
||||
#:stopdoc:
|
||||
def TMail.random_tag #:nodoc:
|
||||
@uniq += 1
|
||||
t = Time.now
|
||||
sprintf('%x%x_%x%x%d%x',
|
||||
|
@ -54,50 +101,55 @@ module TMail
|
|||
|
||||
@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
|
||||
# Defines characters per RFC that are OK for TOKENs, ATOMs, PHRASEs and CONTROL characters.
|
||||
|
||||
aspecial = '()<>[]:;.\\,"'
|
||||
tspecial = '()<>[];:\\,"/?='
|
||||
lwsp = " \t\r\n"
|
||||
control = '\x00-\x1f\x7f-\xff'
|
||||
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
|
||||
CONTROL_CHAR = /[#{control}]/n
|
||||
|
||||
# Returns true if the string supplied is free from characters not allowed as an ATOM
|
||||
def atom_safe?( str )
|
||||
# Returns true if the string supplied is free from characters not allowed as an ATOM
|
||||
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 )
|
||||
# If the string supplied has ATOM unsafe characters in it, will return the string quoted
|
||||
# in double quotes, otherwise returns the string unmodified
|
||||
(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 )
|
||||
# If the string supplied has PHRASE unsafe characters in it, will return the string quoted
|
||||
# in double quotes, otherwise returns the string unmodified
|
||||
(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 )
|
||||
# Returns true if the string supplied is free from characters not allowed as a TOKEN
|
||||
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 )
|
||||
# If the string supplied has TOKEN unsafe characters in it, will return the string quoted
|
||||
# in double quotes, otherwise returns the string unmodified
|
||||
(TOKEN_UNSAFE === str) ? dquote(str) : str
|
||||
end
|
||||
|
||||
def dquote( str )
|
||||
# Wraps supplied string in double quotes unless it is already wrapped
|
||||
# Returns double quoted string
|
||||
# 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
|
||||
|
@ -106,12 +158,14 @@ module TMail
|
|||
end
|
||||
private :dquote
|
||||
|
||||
# Unwraps supplied string from inside double quotes
|
||||
# Returns unquoted string
|
||||
def unquote( str )
|
||||
# Unwraps supplied string from inside double quotes
|
||||
# Returns unquoted string
|
||||
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
|
||||
|
@ -122,7 +176,7 @@ module TMail
|
|||
}.join('.')
|
||||
end
|
||||
|
||||
|
||||
#:stopdoc:
|
||||
ZONESTR_TABLE = {
|
||||
'jst' => 9 * 60,
|
||||
'eet' => 2 * 60,
|
||||
|
@ -168,9 +222,10 @@ module TMail
|
|||
'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 )
|
||||
# Takes a time zone string from an EMail and converts it to Unix Time (seconds)
|
||||
if m = /([\+\-])(\d\d?)(\d\d)/.match(str)
|
||||
sec = (m[2].to_i * 60 + m[3].to_i) * 60
|
||||
m[1] == '-' ? -sec : sec
|
||||
|
@ -181,7 +236,7 @@ module TMail
|
|||
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 )
|
||||
|
@ -239,7 +294,7 @@ module TMail
|
|||
}
|
||||
|
||||
def to_kcode( str )
|
||||
flag = NKF_FLAGS[$KCODE] or return str
|
||||
flag = NKF_FLAGS[TMail.KCODE] or return str
|
||||
NKF.nkf(flag, str)
|
||||
end
|
||||
|
||||
|
@ -248,8 +303,7 @@ module TMail
|
|||
def decode_RFC2231( str )
|
||||
m = RFC2231_ENCODED.match(str) or return str
|
||||
begin
|
||||
NKF.nkf(NKF_FLAGS[$KCODE],
|
||||
m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr })
|
||||
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
|
||||
|
@ -263,7 +317,7 @@ module TMail
|
|||
preamble = $1
|
||||
remainder = $2
|
||||
if remainder =~ /;/
|
||||
remainder =~ /^(.*)(;.*)$/m
|
||||
remainder =~ /^(.*?)(;.*)$/m
|
||||
boundary_text = $1
|
||||
post = $2.chomp
|
||||
else
|
||||
|
@ -275,6 +329,8 @@ module TMail
|
|||
end
|
||||
end
|
||||
end
|
||||
#:startdoc:
|
||||
|
||||
|
||||
end
|
||||
|
|
@ -27,11 +27,12 @@
|
|||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
module TMail #:nodoc:
|
||||
module VERSION #:nodoc:
|
||||
#:stopdoc:
|
||||
module TMail
|
||||
module VERSION
|
||||
MAJOR = 1
|
||||
MINOR = 1
|
||||
TINY = 1
|
||||
MINOR = 2
|
||||
TINY = 2
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
|
@ -2,7 +2,7 @@ module ActionMailer
|
|||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 0
|
||||
TINY = 2
|
||||
TINY = 991
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
require 'abstract_unit'
|
||||
|
||||
class DefaultDeliveryMethodMailer < ActionMailer::Base
|
||||
end
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 8.4 KiB |
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
|||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
require 'abstract_unit'
|
||||
|
||||
module MailerHelper
|
||||
def person_name
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
require 'abstract_unit'
|
||||
|
||||
class RenderMailer < ActionMailer::Base
|
||||
def inline_template(recipient)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
# encoding: utf-8
|
||||
require 'abstract_unit'
|
||||
|
||||
class FunkyPathMailer < ActionMailer::Base
|
||||
self.template_root = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
|
||||
|
@ -534,7 +535,8 @@ class ActionMailerTest < Test::Unit::TestCase
|
|||
def test_delivery_logs_sent_mail
|
||||
mail = TestMailer.create_signed_up(@recipient)
|
||||
logger = mock()
|
||||
logger.expects(:info).with("Sent mail:\n #{mail.encoded}")
|
||||
logger.expects(:info).with("Sent mail to #{@recipient}")
|
||||
logger.expects(:debug).with("\n#{mail.encoded}")
|
||||
TestMailer.logger = logger
|
||||
TestMailer.deliver_signed_up(@recipient)
|
||||
end
|
||||
|
@ -766,23 +768,23 @@ EOF
|
|||
|
||||
def test_implicitly_multipart_messages
|
||||
mail = TestMailer.create_implicitly_multipart_example(@recipient)
|
||||
assert_equal 6, mail.parts.length
|
||||
assert_equal 3, mail.parts.length
|
||||
assert_equal "1.0", mail.mime_version
|
||||
assert_equal "multipart/alternative", mail.content_type
|
||||
assert_equal "text/yaml", mail.parts[0].content_type
|
||||
assert_equal "utf-8", mail.parts[0].sub_header("content-type", "charset")
|
||||
assert_equal "text/plain", mail.parts[2].content_type
|
||||
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 "text/html", mail.parts[4].content_type
|
||||
assert_equal "utf-8", mail.parts[4].sub_header("content-type", "charset")
|
||||
end
|
||||
|
||||
def test_implicitly_multipart_messages_with_custom_order
|
||||
mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["text/yaml", "text/plain"])
|
||||
assert_equal 6, mail.parts.length
|
||||
assert_equal 3, mail.parts.length
|
||||
assert_equal "text/html", mail.parts[0].content_type
|
||||
assert_equal "text/plain", mail.parts[2].content_type
|
||||
assert_equal "text/yaml", mail.parts[4].content_type
|
||||
assert_equal "text/plain", mail.parts[1].content_type
|
||||
assert_equal "text/yaml", mail.parts[2].content_type
|
||||
end
|
||||
|
||||
def test_implicitly_multipart_messages_with_charset
|
||||
|
@ -838,7 +840,11 @@ EOF
|
|||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email8")
|
||||
mail = TMail::Mail.parse(fixture)
|
||||
attachment = mail.attachments.last
|
||||
assert_equal "01QuienTeDijat.Pitbull.mp3", attachment.original_filename
|
||||
|
||||
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
|
||||
|
|
25
vendor/rails/actionmailer/test/quoting_test.rb
vendored
25
vendor/rails/actionmailer/test/quoting_test.rb
vendored
|
@ -1,9 +1,9 @@
|
|||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
# encoding: utf-8
|
||||
require 'abstract_unit'
|
||||
require 'tmail'
|
||||
require 'tempfile'
|
||||
|
||||
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?="
|
||||
|
@ -38,11 +38,14 @@ class QuotingTest < Test::Unit::TestCase
|
|||
def test_unqoute_iso
|
||||
a ="=?ISO-8859-1?Q?Brosch=FCre_Rand?="
|
||||
b = TMail::Unquoter.unquote_and_convert_to(a, 'iso-8859-1')
|
||||
assert_equal "Brosch\374re Rand", b
|
||||
expected = "Brosch\374re Rand"
|
||||
expected.force_encoding 'iso-8859-1' if expected.respond_to?(:force_encoding)
|
||||
assert_equal expected, b
|
||||
end
|
||||
|
||||
def test_quote_multibyte_chars
|
||||
original = "\303\246 \303\270 and \303\245"
|
||||
original.force_encoding('ASCII-8BIT') if original.respond_to?(:force_encoding)
|
||||
|
||||
result = execute_in_sandbox(<<-CODE)
|
||||
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
||||
|
@ -70,18 +73,7 @@ class QuotingTest < Test::Unit::TestCase
|
|||
assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject
|
||||
end
|
||||
|
||||
def test_decode
|
||||
encoded, decoded = expected_base64_strings
|
||||
assert_equal decoded, TMail::Base64.decode(encoded)
|
||||
end
|
||||
|
||||
def test_encode
|
||||
encoded, decoded = expected_base64_strings
|
||||
assert_equal encoded.length, TMail::Base64.encode(decoded).length
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# This whole thing *could* be much simpler, but I don't think Tempfile,
|
||||
# popen and others exist on all platforms (like Windows).
|
||||
def execute_in_sandbox(code)
|
||||
|
@ -103,9 +95,4 @@ class QuotingTest < Test::Unit::TestCase
|
|||
File.delete(test_name) rescue nil
|
||||
File.delete(res_name) rescue nil
|
||||
end
|
||||
|
||||
def expected_base64_strings
|
||||
[ File.read("#{File.dirname(__FILE__)}/fixtures/raw_base64_encoded_string"), File.read("#{File.dirname(__FILE__)}/fixtures/raw_base64_decoded_string") ]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require File.dirname(__FILE__) + '/abstract_unit'
|
||||
require 'abstract_unit'
|
||||
|
||||
class TestHelperMailer < ActionMailer::Base
|
||||
def test
|
||||
|
@ -9,7 +9,6 @@ class TestHelperMailer < ActionMailer::Base
|
|||
end
|
||||
|
||||
class TestHelperMailerTest < ActionMailer::TestCase
|
||||
|
||||
def test_setup_sets_right_action_mailer_options
|
||||
assert_equal :test, ActionMailer::Base.delivery_method
|
||||
assert ActionMailer::Base.perform_deliveries
|
||||
|
@ -115,3 +114,16 @@ class TestHelperMailerTest < ActionMailer::TestCase
|
|||
assert_match /0 .* but 1/, error.message
|
||||
end
|
||||
end
|
||||
|
||||
class AnotherTestHelperMailerTest < ActionMailer::TestCase
|
||||
tests TestHelperMailer
|
||||
|
||||
def setup
|
||||
@test_var = "a value"
|
||||
end
|
||||
|
||||
def test_setup_shouldnt_conflict_with_mailer_setup
|
||||
assert @expected.is_a?(TMail::Mail)
|
||||
assert_equal 'a value', @test_var
|
||||
end
|
||||
end
|
||||
|
|
2
vendor/rails/actionmailer/test/tmail_test.rb
vendored
2
vendor/rails/actionmailer/test/tmail_test.rb
vendored
|
@ -1,4 +1,4 @@
|
|||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
require 'abstract_unit'
|
||||
|
||||
class TMailMailTest < Test::Unit::TestCase
|
||||
def test_body
|
||||
|
|
2
vendor/rails/actionmailer/test/url_test.rb
vendored
2
vendor/rails/actionmailer/test/url_test.rb
vendored
|
@ -1,4 +1,4 @@
|
|||
require "#{File.dirname(__FILE__)}/abstract_unit"
|
||||
require 'abstract_unit'
|
||||
|
||||
class TestMailer < ActionMailer::Base
|
||||
|
||||
|
|
189
vendor/rails/actionpack/CHANGELOG
vendored
189
vendor/rails/actionpack/CHANGELOG
vendored
|
@ -1,3 +1,192 @@
|
|||
*2.1.0 RC1 (May 11th, 2008)*
|
||||
|
||||
* Fixed that forgery protection can be used without session tracking (Peter Jones) [#139]
|
||||
|
||||
* Added session(:on) to turn session management back on in a controller subclass if the superclass turned it off (Peter Jones) [#136]
|
||||
|
||||
* InstanceTag#default_time_from_options with hash args uses Time.current as default; respects hash settings when time falls in system local spring DST gap [Geoff Buesing]
|
||||
|
||||
* select_date defaults to Time.zone.today when config.time_zone is set [Geoff Buesing]
|
||||
|
||||
* Fixed that TextHelper#text_field would corrypt when raw HTML was used as the value (mchenryc, Kevin Glowacz) [#80]
|
||||
|
||||
* Added ActionController::TestCase#rescue_action_in_public! to control whether the action under test should use the regular rescue_action path instead of simply raising the exception inline (great for error testing) [DHH]
|
||||
|
||||
* Reduce number of instance variables being copied from controller to view. [Pratik]
|
||||
|
||||
* select_datetime and select_time default to Time.zone.now when config.time_zone is set [Geoff Buesing]
|
||||
|
||||
* datetime_select defaults to Time.zone.now when config.time_zone is set [Geoff Buesing]
|
||||
|
||||
* Remove ActionController::Base#view_controller_internals flag. [Pratik]
|
||||
|
||||
* Add conditional options to caches_page method. [Paul Horsfall]
|
||||
|
||||
* Move missing template logic to ActionView. [Pratik]
|
||||
|
||||
* Introduce ActionView::InlineTemplate class. [Pratik]
|
||||
|
||||
* Automatically parse posted JSON content for Mime::JSON requests. [rick]
|
||||
|
||||
POST /posts
|
||||
{"post": {"title": "Breaking News"}}
|
||||
|
||||
def create
|
||||
@post = Post.create params[:post]
|
||||
# ...
|
||||
end
|
||||
|
||||
* add json_escape ERB util to escape html entities in json strings that are output in HTML pages. [rick]
|
||||
|
||||
* Provide a helper proxy to access helper methods from outside views. Closes #10839 [Josh Peek]
|
||||
e.g. ApplicationController.helpers.simple_format(text)
|
||||
|
||||
* Improve documentation. [Xavier Noria, leethal, jerome]
|
||||
|
||||
* Ensure RJS redirect_to doesn't html-escapes string argument. Closes #8546 [josh, eventualbuddha, Pratik]
|
||||
|
||||
* Support render :partial => collection of heterogeneous elements. #11491 [Zach Dennis]
|
||||
|
||||
* Avoid remote_ip spoofing. [Brian Candler]
|
||||
|
||||
* Added support for regexp flags like ignoring case in the :requirements part of routes declarations #11421 [NeilW]
|
||||
|
||||
* Fixed that ActionController::Base#read_multipart would fail if boundary was exactly 10240 bytes #10886 [ariejan]
|
||||
|
||||
* Fixed HTML::Tokenizer (used in sanitize helper) didn't handle unclosed CDATA tags #10071 [esad, packagethief]
|
||||
|
||||
* Improve documentation. [Radar, Jan De Poorter, chuyeow, xaviershay, danger, miloops, Xavier Noria, Sunny Ripert]
|
||||
|
||||
* Fixed that FormHelper#radio_button would produce invalid ids #11298 [harlancrystal]
|
||||
|
||||
* Added :confirm option to submit_tag #11415 [miloops]
|
||||
|
||||
* Fixed NumberHelper#number_with_precision to properly round in a way that works equally on Mac, Windows, Linux (closes #11409, #8275, #10090, #8027) [zhangyuanyi]
|
||||
|
||||
* Allow the #simple_format text_helper to take an html_options hash for each paragraph. #2448 [Francois Beausoleil, thechrisoshow]
|
||||
|
||||
* Fix regression from filter refactoring where re-adding a skipped filter resulted in it being called twice. [rick]
|
||||
|
||||
* Refactor filters to use Active Support callbacks. #11235 [Josh Peek]
|
||||
|
||||
* Fixed that polymorphic routes would modify the input array #11363 [thomas.lee]
|
||||
|
||||
* Added :format option to NumberHelper#number_to_currency to enable better localization support #11149 [lylo]
|
||||
|
||||
* Fixed that TextHelper#excerpt would include one character too many #11268 [Irfy]
|
||||
|
||||
* Fix more obscure nested parameter hash parsing bug. #10797 [thomas.lee]
|
||||
|
||||
* Added ActionView::Helpers::register_javascript/stylesheet_expansion to make it easier for plugin developers to inject multiple assets. #10350 [lotswholetime]
|
||||
|
||||
* Fix nested parameter hash parsing bug. #10797 [thomas.lee]
|
||||
|
||||
* Allow using named routes in ActionController::TestCase before any request has been made. Closes #11273 [alloy]
|
||||
|
||||
* Fixed that sweepers defined by cache_sweeper will be added regardless of the perform_caching setting. Instead, control whether the sweeper should be run with the perform_caching setting. This makes testing easier when you want to turn perform_caching on/off [DHH]
|
||||
|
||||
* Make MimeResponds::Responder#any work without explicit types. Closes #11140 [jaw6]
|
||||
|
||||
* Better error message for type conflicts when parsing params. Closes #7962 [spicycode, matt]
|
||||
|
||||
* Remove unused ActionController::Base.template_class. Closes #10787 [Pratik]
|
||||
|
||||
* Moved template handlers related code from ActionView::Base to ActionView::Template. [Pratik]
|
||||
|
||||
* Tests for div_for and content_tag_for helpers. Closes #11223 [thechrisoshow]
|
||||
|
||||
* Allow file uploads in Integration Tests. Closes #11091 [RubyRedRick]
|
||||
|
||||
* Refactor partial rendering into a PartialTemplate class. [Pratik]
|
||||
|
||||
* Added that requests with JavaScript as the priority mime type in the accept header and no format extension in the parameters will be treated as though their format was :js when it comes to determining which template to render. This makes it possible for JS requests to automatically render action.js.rjs files without an explicit respond_to block [DHH]
|
||||
|
||||
* Tests for distance_of_time_in_words with TimeWithZone instances. Closes #10914 [ernesto.jimenez]
|
||||
|
||||
* Remove support for multivalued (e.g., '&'-delimited) cookies. [Jamis Buck]
|
||||
|
||||
* Fix problem with render :partial collections, records, and locals. #11057 [lotswholetime]
|
||||
|
||||
* Added support for naming concrete classes in sweeper declarations [DHH]
|
||||
|
||||
* Remove ERB trim variables from trace template in case ActionView::Base.erb_trim_mode is changed in the application. #10098 [tpope, kampers]
|
||||
|
||||
* Fix typo in form_helper documentation. #10650 [xaviershay, kampers]
|
||||
|
||||
* Fix bug with setting Request#format= after the getter has cached the value. #10889 [cch1]
|
||||
|
||||
* Correct inconsistencies in RequestForgeryProtection docs. #11032 [mislav]
|
||||
|
||||
* Introduce a Template class to ActionView. #11024 [lifofifo]
|
||||
|
||||
* Introduce the :index option for form_for and fields_for to simplify multi-model forms (see http://railscasts.com/episodes/75). #9883 [rmm5t]
|
||||
|
||||
* Introduce map.resources :cards, :as => 'tarjetas' to use a custom resource name in the URL: cards_path == '/tarjetas'. #10578 [blj]
|
||||
|
||||
* TestSession supports indifferent access. #7372 [tamc, Arsen7, mhackett, julik, jean.helou]
|
||||
|
||||
* Make assert_routing aware of the HTTP method used. #8039 [mpalmer]
|
||||
e.g. assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
|
||||
|
||||
* Make map.root accept a single symbol as an argument to declare an alias. #10818 [bscofield]
|
||||
|
||||
e.g. map.dashboard '/dashboard', :controller=>'dashboard'
|
||||
map.root :dashboard
|
||||
|
||||
* Handle corner case with image_tag when passed 'messed up' image names. #9018 [duncanbeevers, mpalmer]
|
||||
|
||||
* Add label_tag helper for generating elements. #10802 [DefV]
|
||||
|
||||
* Introduce TemplateFinder to handle view paths and lookups. #10800 [Pratik Naik]
|
||||
|
||||
* Performance: optimize route recognition. Large speedup for apps with many resource routes. #10835 [oleganza]
|
||||
|
||||
* Make render :partial recognise form builders and use the _form partial. #10814 [djanowski]
|
||||
|
||||
* Allow users to declare other namespaces when using the atom feed helpers. #10304 [david.calavera]
|
||||
|
||||
* Introduce send_file :x_sendfile => true to send an X-Sendfile response header. [Jeremy Kemper]
|
||||
|
||||
* Fixed ActionView::Helpers::ActiveRecordHelper::form for when protect_from_forgery is used #10739 [jeremyevans]
|
||||
|
||||
* Provide nicer access to HTTP Headers. Instead of request.env["HTTP_REFERRER"] you can now use request.headers["Referrer"]. [Koz]
|
||||
|
||||
* UrlWriter respects relative_url_root. #10748 [Cheah Chu Yeow]
|
||||
|
||||
* The asset_host block takes the controller request as an optional second argument. Example: use a single asset host for SSL requests. #10549 [Cheah Chu Yeow, Peter B, Tom Taylor]
|
||||
|
||||
* Support render :text => nil. #6684 [tjennings, PotatoSalad, Cheah Chu Yeow]
|
||||
|
||||
* assert_response failures include the exception message. #10688 [Seth Rasmussen]
|
||||
|
||||
* All fragment cache keys are now by default prefixed with the "views/" namespace [DHH]
|
||||
|
||||
* Moved the caching stores from ActionController::Caching::Fragments::* to ActiveSupport::Cache::*. If you're explicitly referring to a store, like ActionController::Caching::Fragments::MemoryStore, you need to update that reference with ActiveSupport::Cache::MemoryStore [DHH]
|
||||
|
||||
* Deprecated ActionController::Base.fragment_cache_store for ActionController::Base.cache_store [DHH]
|
||||
|
||||
* Made fragment caching in views work for rjs and builder as well #6642 [zsombor]
|
||||
|
||||
* Fixed rendering of partials with layout when done from site layout #9209 [antramm]
|
||||
|
||||
* Fix atom_feed_helper to comply with the atom spec. Closes #10672 [xaviershay]
|
||||
|
||||
* The tags created do not contain a date (http://feedvalidator.org/docs/error/InvalidTAG.html)
|
||||
* IDs are not guaranteed unique
|
||||
* A default self link was not provided, contrary to the documentation
|
||||
* NOTE: This changes tags for existing atom entries, but at least they validate now.
|
||||
|
||||
* Correct indentation in tests. Closes #10671 [l.guidi]
|
||||
|
||||
* Fix that auto_link looks for ='s in url paths (Amazon urls have them). Closes #10640 [bgreenlee]
|
||||
|
||||
* Ensure that test case setup is run even if overridden. #10382 [Josh Peek]
|
||||
|
||||
* Fix HTML Sanitizer to allow trailing spaces in CSS style attributes. Closes #10566 [wesley.moxam]
|
||||
|
||||
* Add :default option to time_zone_select. #10590 [Matt Aimonetti]
|
||||
|
||||
|
||||
*2.0.2* (December 16th, 2007)
|
||||
|
||||
* Added delete_via_redirect and put_via_redirect to integration testing #10497 [philodespotos]
|
||||
|
|
2
vendor/rails/actionpack/MIT-LICENSE
vendored
2
vendor/rails/actionpack/MIT-LICENSE
vendored
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2004-2007 David Heinemeier Hansson
|
||||
Copyright (c) 2004-2008 David Heinemeier Hansson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
|
2
vendor/rails/actionpack/README
vendored
2
vendor/rails/actionpack/README
vendored
|
@ -97,7 +97,7 @@ A short rundown of the major features:
|
|||
|
||||
class WeblogController < ActionController::Base
|
||||
before_filter :authenticate, :cache, :audit
|
||||
after_filter { |c| c.response.body = GZip::compress(c.response.body) }
|
||||
after_filter { |c| c.response.body = Gzip::compress(c.response.body) }
|
||||
after_filter LocalizeFilter
|
||||
|
||||
def index
|
||||
|
|
9
vendor/rails/actionpack/Rakefile
vendored
9
vendor/rails/actionpack/Rakefile
vendored
|
@ -4,7 +4,7 @@ require 'rake/testtask'
|
|||
require 'rake/rdoctask'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
require 'rake/contrib/sshpublisher'
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
|
||||
|
||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||
|
@ -27,9 +27,9 @@ task :test => [:test_action_pack, :test_active_record_integration]
|
|||
|
||||
Rake::TestTask.new(:test_action_pack) { |t|
|
||||
t.libs << "test"
|
||||
# make sure we include the controller tests (c*) first as on some systems
|
||||
# make sure we include the tests in alphabetical order as on some systems
|
||||
# this will not happen automatically and the tests (as a whole) will error
|
||||
t.test_files=Dir.glob( "test/c*/**/*_test.rb" ) + Dir.glob( "test/[ft]*/*_test.rb" )
|
||||
t.test_files=Dir.glob( "test/[cft]*/**/*_test.rb" ).sort
|
||||
# t.pattern = 'test/*/*_test.rb'
|
||||
t.verbose = true
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
|
||||
s.add_dependency('activesupport', '= 2.0.2' + PKG_BUILD)
|
||||
s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD)
|
||||
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'action_controller'
|
||||
|
@ -144,6 +144,7 @@ end
|
|||
desc "Publish the release files to RubyForge."
|
||||
task :release => [ :package ] do
|
||||
require 'rubyforge'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
|
||||
packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#--
|
||||
# Copyright (c) 2004-2007 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2008 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
@ -55,9 +55,9 @@ require 'action_controller/http_authentication'
|
|||
require 'action_controller/components'
|
||||
require 'action_controller/record_identifier'
|
||||
require 'action_controller/request_forgery_protection'
|
||||
require 'action_controller/headers'
|
||||
|
||||
require 'action_view'
|
||||
ActionController::Base.template_class = ActionView::Base
|
||||
|
||||
ActionController::Base.class_eval do
|
||||
include ActionController::Flash
|
||||
|
|
|
@ -33,7 +33,13 @@ module ActionController
|
|||
elsif type.is_a?(Symbol) && @response.response_code == ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE[type]
|
||||
assert_block("") { true } # to count the assertion
|
||||
else
|
||||
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
|
||||
if @response.error?
|
||||
exception = @response.template.instance_variable_get(:@exception)
|
||||
exception_message = exception && exception.message
|
||||
assert_block(build_message(message, "Expected response to be a <?>, but was <?>\n<?>", type, @response.response_code, exception_message.to_s)) { false }
|
||||
else
|
||||
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -114,6 +114,9 @@ module ActionController
|
|||
#
|
||||
# # Tests a route, providing a defaults hash
|
||||
# assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"}
|
||||
#
|
||||
# # Tests a route with a HTTP method
|
||||
# assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
|
||||
def assert_routing(path, options, defaults={}, extras={}, message=nil)
|
||||
assert_recognizes(options, path, extras, message)
|
||||
|
||||
|
@ -122,7 +125,7 @@ module ActionController
|
|||
options[:controller] = "/#{controller}"
|
||||
end
|
||||
|
||||
assert_generates(path, options, defaults, extras, message)
|
||||
assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -21,11 +21,11 @@ module ActionController
|
|||
# from the response HTML or elements selected by the enclosing assertion.
|
||||
#
|
||||
# In addition to HTML responses, you can make the following assertions:
|
||||
# * #assert_select_rjs -- Assertions on HTML content of RJS update and
|
||||
# * +assert_select_rjs+ - Assertions on HTML content of RJS update and
|
||||
# insertion operations.
|
||||
# * #assert_select_encoded -- Assertions on HTML encoded inside XML,
|
||||
# * +assert_select_encoded+ - Assertions on HTML encoded inside XML,
|
||||
# for example for dealing with feed item descriptions.
|
||||
# * #assert_select_email -- Assertions on the HTML body of an e-mail.
|
||||
# * +assert_select_email+ - Assertions on the HTML body of an e-mail.
|
||||
#
|
||||
# Also see HTML::Selector to learn how to use selectors.
|
||||
module SelectorAssertions
|
||||
|
@ -136,27 +136,27 @@ module ActionController
|
|||
# === Equality Tests
|
||||
#
|
||||
# The equality test may be one of the following:
|
||||
# * <tt>true</tt> -- Assertion is true if at least one element selected.
|
||||
# * <tt>false</tt> -- Assertion is true if no element selected.
|
||||
# * <tt>String/Regexp</tt> -- Assertion is true if the text value of at least
|
||||
# * <tt>true</tt> - Assertion is true if at least one element selected.
|
||||
# * <tt>false</tt> - Assertion is true if no element selected.
|
||||
# * <tt>String/Regexp</tt> - Assertion is true if the text value of at least
|
||||
# one element matches the string or regular expression.
|
||||
# * <tt>Integer</tt> -- Assertion is true if exactly that number of
|
||||
# * <tt>Integer</tt> - Assertion is true if exactly that number of
|
||||
# elements are selected.
|
||||
# * <tt>Range</tt> -- Assertion is true if the number of selected
|
||||
# * <tt>Range</tt> - Assertion is true if the number of selected
|
||||
# elements fit the range.
|
||||
# If no equality test specified, the assertion is true if at least one
|
||||
# element selected.
|
||||
#
|
||||
# To perform more than one equality tests, use a hash with the following keys:
|
||||
# * <tt>:text</tt> -- Narrow the selection to elements that have this text
|
||||
# * <tt>:text</tt> - Narrow the selection to elements that have this text
|
||||
# value (string or regexp).
|
||||
# * <tt>:html</tt> -- Narrow the selection to elements that have this HTML
|
||||
# * <tt>:html</tt> - Narrow the selection to elements that have this HTML
|
||||
# content (string or regexp).
|
||||
# * <tt>:count</tt> -- Assertion is true if the number of selected elements
|
||||
# * <tt>:count</tt> - Assertion is true if the number of selected elements
|
||||
# is equal to this value.
|
||||
# * <tt>:minimum</tt> -- Assertion is true if the number of selected
|
||||
# * <tt>:minimum</tt> - Assertion is true if the number of selected
|
||||
# elements is at least this value.
|
||||
# * <tt>:maximum</tt> -- Assertion is true if the number of selected
|
||||
# * <tt>:maximum</tt> - Assertion is true if the number of selected
|
||||
# elements is at most this value.
|
||||
#
|
||||
# If the method is called with a block, once all equality tests are
|
||||
|
@ -263,12 +263,15 @@ module ActionController
|
|||
if match_with = equals[:text]
|
||||
matches.delete_if do |match|
|
||||
text = ""
|
||||
text.force_encoding(match_with.encoding) if text.respond_to?(:force_encoding)
|
||||
stack = match.children.reverse
|
||||
while node = stack.pop
|
||||
if node.tag?
|
||||
stack.concat node.children.reverse
|
||||
else
|
||||
text << node.content
|
||||
content = node.content
|
||||
content.force_encoding(match_with.encoding) if content.respond_to?(:force_encoding)
|
||||
text << content
|
||||
end
|
||||
end
|
||||
text.strip! unless NO_STRIP.include?(match.name)
|
||||
|
|
|
@ -5,6 +5,7 @@ require 'action_controller/routing'
|
|||
require 'action_controller/resources'
|
||||
require 'action_controller/url_rewriter'
|
||||
require 'action_controller/status_codes'
|
||||
require 'action_view'
|
||||
require 'drb'
|
||||
require 'set'
|
||||
|
||||
|
@ -15,9 +16,6 @@ module ActionController #:nodoc:
|
|||
class SessionRestoreError < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class MissingTemplate < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
class RenderError < ActionControllerError #:nodoc:
|
||||
end
|
||||
|
||||
|
@ -161,28 +159,34 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Hello #{session[:person]}
|
||||
#
|
||||
# For removing objects from the session, you can either assign a single key to nil, like <tt>session[:person] = nil</tt>, or you can
|
||||
# remove the entire session with reset_session.
|
||||
# For removing objects from the session, you can either assign a single key to +nil+:
|
||||
#
|
||||
# Sessions are stored in a browser cookie that's cryptographically signed, but unencrypted, by default. This prevents
|
||||
# the user from tampering with the session but also allows him to see its contents.
|
||||
# # removes :person from session
|
||||
# session[:person] = nil
|
||||
#
|
||||
# Do not put secret information in session!
|
||||
# or you can remove the entire session with +reset_session+.
|
||||
#
|
||||
# Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted.
|
||||
# This prevents the user from tampering with the session but also allows him to see its contents.
|
||||
#
|
||||
# Do not put secret information in cookie-based sessions!
|
||||
#
|
||||
# Other options for session storage are:
|
||||
#
|
||||
# ActiveRecordStore: sessions are stored in your database, which works better than PStore with multiple app servers and,
|
||||
# unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set
|
||||
# * ActiveRecordStore - Sessions are stored in your database, which works better than PStore with multiple app servers and,
|
||||
# unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set
|
||||
#
|
||||
# config.action_controller.session_store = :active_record_store
|
||||
# config.action_controller.session_store = :active_record_store
|
||||
#
|
||||
# in your <tt>environment.rb</tt> and run <tt>rake db:sessions:create</tt>.
|
||||
# in your <tt>config/environment.rb</tt> and run <tt>rake db:sessions:create</tt>.
|
||||
#
|
||||
# MemCacheStore: sessions are stored as entries in your memcached cache. Set the session store type in <tt>environment.rb</tt>:
|
||||
# * MemCacheStore - Sessions are stored as entries in your memcached cache.
|
||||
# Set the session store type in <tt>config/environment.rb</tt>:
|
||||
#
|
||||
# config.action_controller.session_store = :mem_cache_store
|
||||
# config.action_controller.session_store = :mem_cache_store
|
||||
#
|
||||
# This assumes that memcached has been installed and configured properly. See the MemCacheStore docs for more information.
|
||||
# This assumes that memcached has been installed and configured properly.
|
||||
# See the MemCacheStore docs for more information.
|
||||
#
|
||||
# == Responses
|
||||
#
|
||||
|
@ -256,14 +260,10 @@ module ActionController #:nodoc:
|
|||
|
||||
include StatusCodes
|
||||
|
||||
# Determines whether the view has access to controller internals @request, @response, @session, and @template.
|
||||
# By default, it does.
|
||||
@@view_controller_internals = true
|
||||
cattr_accessor :view_controller_internals
|
||||
|
||||
# Protected instance variable cache
|
||||
@@protected_variables_cache = nil
|
||||
cattr_accessor :protected_variables_cache
|
||||
# Controller specific instance variables which will not be accessible inside views.
|
||||
@@protected_view_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
|
||||
@action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params
|
||||
@_flash @_response)
|
||||
|
||||
# Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
|
||||
# and images to a dedicated asset server away from the main web server. Example:
|
||||
|
@ -283,9 +283,10 @@ module ActionController #:nodoc:
|
|||
@@debug_routes = true
|
||||
cattr_accessor :debug_routes
|
||||
|
||||
# Controls whether the application is thread-safe, so multi-threaded servers like WEBrick know whether to apply a mutex
|
||||
# around the performance of each action. Action Pack and Active Record are by default thread-safe, but many applications
|
||||
# may not be. Turned off by default.
|
||||
# Indicates to Mongrel or Webrick whether to allow concurrent action
|
||||
# processing. Your controller actions and any other code they call must
|
||||
# also behave well when called from concurrent threads. Turned off by
|
||||
# default.
|
||||
@@allow_concurrency = false
|
||||
cattr_accessor :allow_concurrency
|
||||
|
||||
|
@ -315,9 +316,10 @@ module ActionController #:nodoc:
|
|||
# A YAML parser is also available and can be turned on with:
|
||||
#
|
||||
# ActionController::Base.param_parsers[Mime::YAML] = :yaml
|
||||
@@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
|
||||
@@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
|
||||
Mime::URL_ENCODED_FORM => :url_encoded_form,
|
||||
Mime::XML => :xml_simple }
|
||||
Mime::XML => :xml_simple,
|
||||
Mime::JSON => :json }
|
||||
cattr_accessor :param_parsers
|
||||
|
||||
# Controls the default charset for all renders.
|
||||
|
@ -328,17 +330,16 @@ module ActionController #:nodoc:
|
|||
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
|
||||
cattr_accessor :logger
|
||||
|
||||
# Determines which template class should be used by ActionController.
|
||||
cattr_accessor :template_class
|
||||
|
||||
# Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
|
||||
cattr_accessor :ignore_missing_templates
|
||||
|
||||
# Controls the resource action separator
|
||||
@@resource_action_separator = "/"
|
||||
cattr_accessor :resource_action_separator
|
||||
|
||||
# Sets the token parameter name for RequestForgery. Calling #protect_from_forgery sets it to :authenticity_token by default
|
||||
# Allow to override path names for default resources' actions
|
||||
@@resources_path_names = { :new => 'new', :edit => 'edit' }
|
||||
cattr_accessor :resources_path_names
|
||||
|
||||
# Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
|
||||
# sets it to <tt>:authenticity_token</tt> by default.
|
||||
cattr_accessor :request_forgery_protection_token
|
||||
|
||||
# Indicates whether or not optimise the generated named
|
||||
|
@ -428,6 +429,7 @@ module ActionController #:nodoc:
|
|||
|
||||
def view_paths=(value)
|
||||
@view_paths = value
|
||||
ActionView::TemplateFinder.process_view_paths(value)
|
||||
end
|
||||
|
||||
# Adds a view_path to the front of the view_paths array.
|
||||
|
@ -440,6 +442,7 @@ module ActionController #:nodoc:
|
|||
def prepend_view_path(path)
|
||||
@view_paths = superclass.view_paths.dup if @view_paths.nil?
|
||||
view_paths.unshift(*path)
|
||||
ActionView::TemplateFinder.process_view_paths(path)
|
||||
end
|
||||
|
||||
# Adds a view_path to the end of the view_paths array.
|
||||
|
@ -452,6 +455,7 @@ module ActionController #:nodoc:
|
|||
def append_view_path(path)
|
||||
@view_paths = superclass.view_paths.dup if @view_paths.nil?
|
||||
view_paths.push(*path)
|
||||
ActionView::TemplateFinder.process_view_paths(path)
|
||||
end
|
||||
|
||||
# Replace sensitive parameter data from the request log.
|
||||
|
@ -534,23 +538,23 @@ module ActionController #:nodoc:
|
|||
|
||||
# Returns a URL that has been rewritten according to the options hash and the defined Routes.
|
||||
# (For doing a complete redirect, use redirect_to).
|
||||
#
|
||||
#
|
||||
# <tt>url_for</tt> is used to:
|
||||
#
|
||||
# All keys given to url_for are forwarded to the Route module, save for the following:
|
||||
# * <tt>:anchor</tt> -- specifies the anchor name to be appended to the path. For example,
|
||||
#
|
||||
# All keys given to +url_for+ are forwarded to the Route module, save for the following:
|
||||
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path. For example,
|
||||
# <tt>url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments'</tt>
|
||||
# will produce "/posts/show/10#comments".
|
||||
# * <tt>:only_path</tt> -- if true, returns the relative URL (omitting the protocol, host name, and port) (<tt>false</tt> by default)
|
||||
# * <tt>:trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
|
||||
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>false</tt> by default).
|
||||
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
|
||||
# is currently not recommended since it breaks caching.
|
||||
# * <tt>:host</tt> -- overrides the default (current) host if provided.
|
||||
# * <tt>:protocol</tt> -- overrides the default (current) protocol if provided.
|
||||
# * <tt>:port</tt> -- optionally specify the port to connect to.
|
||||
# * <tt>:user</tt> -- Inline HTTP authentication (only plucked out if :password is also present).
|
||||
# * <tt>:password</tt> -- Inline HTTP authentication (only plucked out if :user is also present).
|
||||
# * <tt>:skip_relative_url_root</tt> -- if true, the url is not constructed using the relative_url_root of the request so the path
|
||||
# will include the web server relative installation directory.
|
||||
# * <tt>:host</tt> - Overrides the default (current) host if provided.
|
||||
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
|
||||
# * <tt>:port</tt> - Optionally specify the port to connect to.
|
||||
# * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
|
||||
# * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
|
||||
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the +relative_url_root+
|
||||
# of the request so the path will include the web server relative installation directory.
|
||||
#
|
||||
# The URL is generated from the remaining keys in the hash. A URL contains two key parts: the <base> and a query string.
|
||||
# Routes composes a query string as the key/value pairs not included in the <base>.
|
||||
|
@ -601,7 +605,7 @@ module ActionController #:nodoc:
|
|||
# url_for :controller => 'posts', :action => nil
|
||||
#
|
||||
# If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the
|
||||
# :overwrite_params options. Say for your posts you have different views for showing and printing them.
|
||||
# <tt>:overwrite_params</tt> options. Say for your posts you have different views for showing and printing them.
|
||||
# Then, in the show view, you get the URL for the print view like this
|
||||
#
|
||||
# url_for :overwrite_params => { :action => 'print' }
|
||||
|
@ -642,11 +646,11 @@ module ActionController #:nodoc:
|
|||
|
||||
# View load paths for controller.
|
||||
def view_paths
|
||||
(@template || self.class).view_paths
|
||||
@template.finder.view_paths
|
||||
end
|
||||
|
||||
def view_paths=(value)
|
||||
(@template || self.class).view_paths = value
|
||||
@template.finder.view_paths = value # Mutex needed
|
||||
end
|
||||
|
||||
# Adds a view_path to the front of the view_paths array.
|
||||
|
@ -656,7 +660,7 @@ module ActionController #:nodoc:
|
|||
# self.prepend_view_path(["views/default", "views/custom"])
|
||||
#
|
||||
def prepend_view_path(path)
|
||||
(@template || self.class).prepend_view_path(path)
|
||||
@template.finder.prepend_view_path(path) # Mutex needed
|
||||
end
|
||||
|
||||
# Adds a view_path to the end of the view_paths array.
|
||||
|
@ -666,7 +670,7 @@ module ActionController #:nodoc:
|
|||
# self.append_view_path(["views/default", "views/custom"])
|
||||
#
|
||||
def append_view_path(path)
|
||||
(@template || self.class).append_view_path(path)
|
||||
@template.finder.append_view_path(path) # Mutex needed
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -772,7 +776,7 @@ module ActionController #:nodoc:
|
|||
# # placed in "app/views/layouts/special.r(html|xml)"
|
||||
# render :text => "Hi there!", :layout => "special"
|
||||
#
|
||||
# The :text option can also accept a Proc object, which can be used to manually control the page generation. This should
|
||||
# The <tt>:text</tt> option can also accept a Proc object, which can be used to manually control the page generation. This should
|
||||
# generally be avoided, as it violates the separation between code and content, and because almost everything that can be
|
||||
# done with this method can also be done more cleanly using one of the other rendering methods, most notably templates.
|
||||
#
|
||||
|
@ -826,19 +830,21 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# === Rendering with status and location headers
|
||||
#
|
||||
# All renders take the :status and :location options and turn them into headers. They can even be used together:
|
||||
# All renders take the <tt>:status</tt> and <tt>:location</tt> options and turn them into headers. They can even be used together:
|
||||
#
|
||||
# render :xml => post.to_xml, :status => :created, :location => post_url(post)
|
||||
def render(options = nil, &block) #:doc:
|
||||
def render(options = nil, extra_options = {}, &block) #:doc:
|
||||
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
|
||||
|
||||
if options.nil?
|
||||
return render_for_file(default_template_name, nil, true)
|
||||
elsif !extra_options.is_a?(Hash)
|
||||
raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
|
||||
else
|
||||
if options == :update
|
||||
options = { :update => true }
|
||||
options = extra_options.merge({ :update => true })
|
||||
elsif !options.is_a?(Hash)
|
||||
raise RenderError, "You called render with invalid options : #{options}"
|
||||
raise RenderError, "You called render with invalid options : #{options.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -850,8 +856,8 @@ module ActionController #:nodoc:
|
|||
response.headers["Location"] = url_for(location)
|
||||
end
|
||||
|
||||
if text = options[:text]
|
||||
render_for_text(text, options[:status])
|
||||
if options.has_key?(:text)
|
||||
render_for_text(options[:text], options[:status])
|
||||
|
||||
else
|
||||
if file = options[:file]
|
||||
|
@ -862,7 +868,8 @@ module ActionController #:nodoc:
|
|||
|
||||
elsif inline = options[:inline]
|
||||
add_variables_to_assigns
|
||||
render_for_text(@template.render_template(options[:type], inline, nil, options[:locals] || {}), options[:status])
|
||||
tmpl = ActionView::InlineTemplate.new(@template, options[:inline], options[:locals], options[:type])
|
||||
render_for_text(@template.render_template(tmpl), options[:status])
|
||||
|
||||
elsif action_name = options[:action]
|
||||
template = default_template_name(action_name.to_s)
|
||||
|
@ -904,7 +911,7 @@ module ActionController #:nodoc:
|
|||
|
||||
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
|
||||
response.content_type = Mime::JS
|
||||
render_for_text(generator.to_s)
|
||||
render_for_text(generator.to_s, options[:status])
|
||||
|
||||
elsif options[:nothing]
|
||||
# Safari doesn't pass the headers of the return if the response is zero length
|
||||
|
@ -997,7 +1004,7 @@ module ActionController #:nodoc:
|
|||
# As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
|
||||
# urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
|
||||
# by this method.
|
||||
def default_url_options(options) #:doc:
|
||||
def default_url_options(options = nil)
|
||||
end
|
||||
|
||||
# Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
|
||||
|
@ -1029,6 +1036,7 @@ module ActionController #:nodoc:
|
|||
# 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?
|
||||
|
||||
if options.is_a?(Hash) && options[:status]
|
||||
status = options.delete(:status)
|
||||
|
@ -1095,7 +1103,6 @@ module ActionController #:nodoc:
|
|||
private
|
||||
def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
|
||||
add_variables_to_assigns
|
||||
assert_existence_of_template_file(template_path) if use_full_path
|
||||
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
|
||||
render_for_text(@template.render_file(template_path, use_full_path, locals), status)
|
||||
end
|
||||
|
@ -1114,11 +1121,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def initialize_template_class(response)
|
||||
unless @@template_class
|
||||
raise "You must assign a template class through ActionController.template_class= before processing a request"
|
||||
end
|
||||
|
||||
response.template = ActionView::Base.new(view_paths, {}, self)
|
||||
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
|
||||
response.template.extend self.class.master_helper_module
|
||||
response.redirected_to = nil
|
||||
@performed_render = @performed_redirect = false
|
||||
|
@ -1195,7 +1198,6 @@ module ActionController #:nodoc:
|
|||
def add_variables_to_assigns
|
||||
unless @variables_added
|
||||
add_instance_variables_to_assigns
|
||||
add_class_variables_to_assigns if view_controller_internals
|
||||
@variables_added = true
|
||||
end
|
||||
end
|
||||
|
@ -1209,30 +1211,11 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def add_instance_variables_to_assigns
|
||||
@@protected_variables_cache ||= Set.new(protected_instance_variables)
|
||||
instance_variables.each do |var|
|
||||
next if @@protected_variables_cache.include?(var)
|
||||
(instance_variable_names - @@protected_view_variables).each do |var|
|
||||
@assigns[var[1..-1]] = instance_variable_get(var)
|
||||
end
|
||||
end
|
||||
|
||||
def add_class_variables_to_assigns
|
||||
%w(view_paths logger template_class ignore_missing_templates).each do |cvar|
|
||||
@assigns[cvar] = self.send(cvar)
|
||||
end
|
||||
end
|
||||
|
||||
def protected_instance_variables
|
||||
if view_controller_internals
|
||||
%w(@assigns @performed_redirect @performed_render)
|
||||
else
|
||||
%w(@assigns @performed_redirect @performed_render
|
||||
@_request @request @_response @response @_params @params
|
||||
@_session @session @_cookies @cookies
|
||||
@template @request_origin @parent_controller)
|
||||
end
|
||||
end
|
||||
|
||||
def request_origin
|
||||
# this *needs* to be cached!
|
||||
# otherwise you'd get different results if calling it more than once
|
||||
|
@ -1248,7 +1231,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def template_exists?(template_name = default_template_name)
|
||||
@template.file_exists?(template_name)
|
||||
@template.finder.file_exists?(template_name)
|
||||
end
|
||||
|
||||
def template_public?(template_name = default_template_name)
|
||||
|
@ -1256,20 +1239,11 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def template_exempt_from_layout?(template_name = default_template_name)
|
||||
extension = @template && @template.pick_template_extension(template_name)
|
||||
extension = @template && @template.finder.pick_template_extension(template_name)
|
||||
name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name
|
||||
@@exempt_from_layout.any? { |ext| name_with_extension =~ ext }
|
||||
end
|
||||
|
||||
def assert_existence_of_template_file(template_name)
|
||||
unless template_exists?(template_name) || ignore_missing_templates
|
||||
full_template_path = template_name.include?('.') ? template_name : "#{template_name}.#{@template.template_format}.erb"
|
||||
display_paths = view_paths.join(':')
|
||||
template_type = (template_name =~ /layouts/i) ? 'layout' : 'template'
|
||||
raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}")
|
||||
end
|
||||
end
|
||||
|
||||
def default_template_name(action_name = self.action_name)
|
||||
if action_name
|
||||
action_name = action_name.to_s
|
||||
|
|
|
@ -41,14 +41,14 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
protected
|
||||
def render_with_benchmark(options = nil, deprecated_status = nil, &block)
|
||||
def render_with_benchmark(options = nil, extra_options = {}, &block)
|
||||
unless logger
|
||||
render_without_benchmark(options, &block)
|
||||
render_without_benchmark(options, extra_options, &block)
|
||||
else
|
||||
db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
|
||||
render_output = nil
|
||||
@rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, &block) }.real
|
||||
@rendering_runtime = Benchmark::realtime{ render_output = render_without_benchmark(options, extra_options, &block) }
|
||||
|
||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||
@db_rt_before_render = db_runtime
|
||||
|
|
|
@ -2,6 +2,13 @@ require 'fileutils'
|
|||
require 'uri'
|
||||
require 'set'
|
||||
|
||||
require 'action_controller/caching/pages'
|
||||
require 'action_controller/caching/actions'
|
||||
require 'action_controller/caching/sql_cache'
|
||||
require 'action_controller/caching/sweeping'
|
||||
require 'action_controller/caching/fragments'
|
||||
|
||||
|
||||
module ActionController #:nodoc:
|
||||
# Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
|
||||
# around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment.
|
||||
|
@ -9,675 +16,57 @@ module ActionController #:nodoc:
|
|||
# You can read more about each approach and the sweeping assistance by clicking the modules below.
|
||||
#
|
||||
# Note: To turn off all caching and sweeping, set Base.perform_caching = false.
|
||||
#
|
||||
#
|
||||
# == Caching stores
|
||||
#
|
||||
# All the caching stores from ActiveSupport::Cache is available to be used as backends for Action Controller caching. This setting only
|
||||
# affects action and fragment caching as page caching is always written to disk.
|
||||
#
|
||||
# Configuration examples (MemoryStore is the default):
|
||||
#
|
||||
# ActionController::Base.cache_store = :memory_store
|
||||
# ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
|
||||
# ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
|
||||
# ActionController::Base.cache_store = :mem_cache_store, "localhost"
|
||||
# ActionController::Base.cache_store = MyOwnStore.new("parameter")
|
||||
module Caching
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
include Pages, Actions, Fragments
|
||||
@@cache_store = nil
|
||||
cattr_reader :cache_store
|
||||
|
||||
if defined? ActiveRecord
|
||||
include Sweeping, SqlCache
|
||||
# Defines the storage option for cached fragments
|
||||
def self.cache_store=(store_option)
|
||||
@@cache_store = ActiveSupport::Cache.lookup_store(store_option)
|
||||
end
|
||||
|
||||
include Pages, Actions, Fragments
|
||||
include Sweeping, SqlCache if defined?(ActiveRecord)
|
||||
|
||||
@@perform_caching = true
|
||||
cattr_accessor :perform_caching
|
||||
|
||||
def self.cache_configured?
|
||||
perform_caching && cache_store
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
|
||||
# can serve without going through the Action Pack. This can be as much as 100 times faster than going through the process of dynamically
|
||||
# generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors
|
||||
# are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit
|
||||
# for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates.
|
||||
#
|
||||
# Specifying which actions to cache is done through the <tt>caches</tt> class method:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# caches_page :show, :new
|
||||
# end
|
||||
#
|
||||
# This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic
|
||||
# generation. This is how the web server is able pick up a cache file when it exists and otherwise let the request pass on to
|
||||
# the Action Pack to generate it.
|
||||
#
|
||||
# Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
|
||||
# is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# def update
|
||||
# List.update(params[:list][:id], params[:list])
|
||||
# expire_page :action => "show", :id => params[:list][:id]
|
||||
# redirect_to :action => "show", :id => params[:list][:id]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
|
||||
# expired.
|
||||
#
|
||||
# == Setting the cache directory
|
||||
#
|
||||
# The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root".
|
||||
# For Rails, this directory has already been set to RAILS_ROOT + "/public".
|
||||
#
|
||||
# == Setting the cache extension
|
||||
#
|
||||
# By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want
|
||||
# something else, like .php or .shtml, just set Base.page_cache_extension.
|
||||
module Pages
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
@@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : ""
|
||||
cattr_accessor :page_cache_directory
|
||||
|
||||
@@page_cache_extension = '.html'
|
||||
cattr_accessor :page_cache_extension
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Expires the page that was cached with the +path+ as a key. Example:
|
||||
# expire_page "/lists/show"
|
||||
def expire_page(path)
|
||||
return unless perform_caching
|
||||
|
||||
benchmark "Expired page: #{page_cache_file(path)}" do
|
||||
File.delete(page_cache_path(path)) if File.exist?(page_cache_path(path))
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +path+. Example:
|
||||
# cache_page "I'm the cached content", "/lists/show"
|
||||
def cache_page(content, path)
|
||||
return unless perform_caching
|
||||
|
||||
benchmark "Cached page: #{page_cache_file(path)}" do
|
||||
FileUtils.makedirs(File.dirname(page_cache_path(path)))
|
||||
File.open(page_cache_path(path), "wb+") { |f| f.write(content) }
|
||||
end
|
||||
end
|
||||
|
||||
# Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
|
||||
# matches the triggering url.
|
||||
def caches_page(*actions)
|
||||
return unless perform_caching
|
||||
actions = actions.map(&:to_s)
|
||||
after_filter { |c| c.cache_page if actions.include?(c.action_name) }
|
||||
end
|
||||
|
||||
private
|
||||
def page_cache_file(path)
|
||||
name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/'))
|
||||
name << page_cache_extension unless (name.split('/').last || name).include? '.'
|
||||
return name
|
||||
end
|
||||
|
||||
def page_cache_path(path)
|
||||
page_cache_directory + page_cache_file(path)
|
||||
end
|
||||
end
|
||||
|
||||
# Expires the page that was cached with the +options+ as a key. Example:
|
||||
# expire_page :controller => "lists", :action => "show"
|
||||
def expire_page(options = {})
|
||||
return unless perform_caching
|
||||
|
||||
if options.is_a?(Hash)
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action)))
|
||||
end
|
||||
else
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
|
||||
end
|
||||
protected
|
||||
# Convenience accessor
|
||||
def cache(key, options = {}, &block)
|
||||
if cache_configured?
|
||||
cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
|
||||
else
|
||||
self.class.expire_page(options)
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used
|
||||
# If no options are provided, the requested url is used. Example:
|
||||
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
|
||||
def cache_page(content = nil, options = nil)
|
||||
return unless perform_caching && caching_allowed
|
||||
|
||||
path = case options
|
||||
when Hash
|
||||
url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format]))
|
||||
when String
|
||||
options
|
||||
else
|
||||
request.path
|
||||
end
|
||||
|
||||
self.class.cache_page(content || response.body, path)
|
||||
end
|
||||
|
||||
private
|
||||
def caching_allowed
|
||||
request.get? && response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
|
||||
# Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
|
||||
# every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which
|
||||
# allows for authentication and other restrictions on whether someone is allowed to see the cache. Example:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :show, :feed
|
||||
# end
|
||||
#
|
||||
# In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
|
||||
# show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
|
||||
#
|
||||
# Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both
|
||||
# the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
|
||||
# "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
|
||||
# "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
|
||||
#
|
||||
# Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt>
|
||||
# are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same
|
||||
# as <tt>:action => 'list', :format => :xml</tt>.
|
||||
#
|
||||
# You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
|
||||
# for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :show, :cache_path => { :project => 1 }
|
||||
# caches_action :show, :cache_path => Proc.new { |controller|
|
||||
# controller.params[:user_id] ?
|
||||
# controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
|
||||
# controller.send(:list_url, c.params[:id]) }
|
||||
# end
|
||||
module Actions
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
attr_accessor :rendered_action_cache, :action_cache_path
|
||||
alias_method_chain :protected_instance_variables, :action_caching
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Declares that +actions+ should be cached.
|
||||
# See ActionController::Caching::Actions for details.
|
||||
def caches_action(*actions)
|
||||
return unless perform_caching
|
||||
around_filter(ActionCacheFilter.new(*actions))
|
||||
end
|
||||
end
|
||||
|
||||
def protected_instance_variables_with_action_caching
|
||||
protected_instance_variables_without_action_caching + %w(@action_cache_path)
|
||||
end
|
||||
|
||||
def expire_action(options = {})
|
||||
return unless perform_caching
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action })))
|
||||
end
|
||||
else
|
||||
expire_fragment(ActionCachePath.path_for(self, options))
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCacheFilter #:nodoc:
|
||||
def initialize(*actions, &block)
|
||||
@options = actions.extract_options!
|
||||
@actions = Set.new actions
|
||||
end
|
||||
|
||||
def before(controller)
|
||||
return unless @actions.include?(controller.action_name.intern)
|
||||
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
|
||||
if cache = controller.read_fragment(cache_path.path)
|
||||
controller.rendered_action_cache = true
|
||||
set_content_type!(controller, cache_path.extension)
|
||||
controller.send!(:render_for_text, cache)
|
||||
false
|
||||
else
|
||||
controller.action_cache_path = cache_path
|
||||
end
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller)
|
||||
controller.write_fragment(controller.action_cache_path.path, controller.response.body)
|
||||
end
|
||||
|
||||
private
|
||||
def set_content_type!(controller, extension)
|
||||
controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension
|
||||
end
|
||||
|
||||
def path_options_for(controller, options)
|
||||
((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {}
|
||||
end
|
||||
|
||||
def caching_allowed(controller)
|
||||
controller.request.get? && controller.response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCachePath
|
||||
attr_reader :path, :extension
|
||||
|
||||
class << self
|
||||
def path_for(controller, options)
|
||||
new(controller, options).path
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(controller, options = {})
|
||||
@extension = extract_extension(controller.request.path)
|
||||
path = controller.url_for(options).split('://').last
|
||||
normalize!(path)
|
||||
add_extension!(path, @extension)
|
||||
@path = URI.unescape(path)
|
||||
end
|
||||
|
||||
private
|
||||
def normalize!(path)
|
||||
path << 'index' if path[-1] == ?/
|
||||
end
|
||||
|
||||
def add_extension!(path, extension)
|
||||
path << ".#{extension}" if extension
|
||||
end
|
||||
|
||||
def extract_extension(file_path)
|
||||
# Don't want just what comes after the last '.' to accommodate multi part extensions
|
||||
# such as tar.gz.
|
||||
file_path[/^[^.]+\.(.+)$/, 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
|
||||
# certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
|
||||
# parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like:
|
||||
#
|
||||
# <b>Hello <%= @name %></b>
|
||||
# <% cache do %>
|
||||
# All the topics in the system:
|
||||
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
|
||||
# <% end %>
|
||||
#
|
||||
# This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would
|
||||
# be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.
|
||||
#
|
||||
# This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using
|
||||
# <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like:
|
||||
#
|
||||
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
|
||||
#
|
||||
# That would result in a name such as "/topics/list/all_topics", avoiding conflicts with the action cache and with any fragments that use a
|
||||
# different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique
|
||||
# cache names that we can refer to when we need to expire the cache.
|
||||
#
|
||||
# The expiration call for this example is:
|
||||
#
|
||||
# expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
|
||||
#
|
||||
# == Fragment stores
|
||||
#
|
||||
# By default, cached fragments are stored in memory. The available store options are:
|
||||
#
|
||||
# * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and allows all
|
||||
# processes running from the same application directory to access the cached content.
|
||||
# * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its
|
||||
# own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take
|
||||
# up a lot of memory since each process keeps all the caches in memory.
|
||||
# * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache
|
||||
# around for all processes, but requires that you run and manage a separate DRb process.
|
||||
# * MemCacheStore: Works like DRbStore, but uses Danga's MemCache instead.
|
||||
# Requires the ruby-memcache library: gem install ruby-memcache.
|
||||
#
|
||||
# Configuration examples (MemoryStore is the default):
|
||||
#
|
||||
# ActionController::Base.fragment_cache_store = :memory_store
|
||||
# ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
|
||||
# ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192"
|
||||
# ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
|
||||
# ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter")
|
||||
module Fragments
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
@@fragment_cache_store = MemoryStore.new
|
||||
cattr_reader :fragment_cache_store
|
||||
|
||||
# Defines the storage option for cached fragments
|
||||
def self.fragment_cache_store=(store_option)
|
||||
store, *parameters = *([ store_option ].flatten)
|
||||
@@fragment_cache_store = if store.is_a?(Symbol)
|
||||
store_class_name = (store == :drb_store ? "DRbStore" : store.to_s.camelize)
|
||||
store_class = ActionController::Caching::Fragments.const_get(store_class_name)
|
||||
store_class.new(*parameters)
|
||||
else
|
||||
store
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Given a name (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
|
||||
# writing, or expiring a cached fragment. If the name is a hash, the generated name is the return
|
||||
# value of url_for on that hash (without the protocol).
|
||||
def fragment_cache_key(name)
|
||||
name.is_a?(Hash) ? url_for(name).split("://").last : name
|
||||
end
|
||||
|
||||
# Called by CacheHelper#cache
|
||||
def cache_erb_fragment(block, name = {}, options = nil)
|
||||
unless perform_caching then block.call; return end
|
||||
|
||||
buffer = eval(ActionView::Base.erb_variable, block.binding)
|
||||
|
||||
if cache = read_fragment(name, options)
|
||||
buffer.concat(cache)
|
||||
else
|
||||
pos = buffer.length
|
||||
block.call
|
||||
write_fragment(name, buffer[pos..-1], options)
|
||||
end
|
||||
end
|
||||
|
||||
# Writes <tt>content</tt> to the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def write_fragment(name, content, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
key = fragment_cache_key(name)
|
||||
self.class.benchmark "Cached fragment: #{key}" do
|
||||
fragment_cache_store.write(key, content, options)
|
||||
end
|
||||
content
|
||||
end
|
||||
|
||||
# Reads a cached fragment from the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def read_fragment(name, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
key = fragment_cache_key(name)
|
||||
self.class.benchmark "Fragment read: #{key}" do
|
||||
fragment_cache_store.read(key, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Name can take one of three forms:
|
||||
# * String: This would normally take the form of a path like "pages/45/notes"
|
||||
# * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
|
||||
# * Regexp: Will destroy all the matched fragments, example:
|
||||
# %r{pages/\d*/notes}
|
||||
# Ensure you do not specify start and finish in the regex (^$) because
|
||||
# the actual filename matched looks like ./cache/filename/path.cache
|
||||
# Regexp expiration is only supported on caches that can iterate over
|
||||
# all keys (unlike memcached).
|
||||
def expire_fragment(name, options = nil)
|
||||
return unless perform_caching
|
||||
|
||||
key = fragment_cache_key(name)
|
||||
|
||||
if key.is_a?(Regexp)
|
||||
self.class.benchmark "Expired fragments matching: #{key.source}" do
|
||||
fragment_cache_store.delete_matched(key, options)
|
||||
end
|
||||
else
|
||||
self.class.benchmark "Expired fragment: #{key}" do
|
||||
fragment_cache_store.delete(key, options)
|
||||
end
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class UnthreadedMemoryStore #:nodoc:
|
||||
def initialize #:nodoc:
|
||||
@data = {}
|
||||
end
|
||||
|
||||
def read(name, options=nil) #:nodoc:
|
||||
@data[name]
|
||||
end
|
||||
|
||||
def write(name, value, options=nil) #:nodoc:
|
||||
@data[name] = value
|
||||
end
|
||||
|
||||
def delete(name, options=nil) #:nodoc:
|
||||
@data.delete(name)
|
||||
end
|
||||
|
||||
def delete_matched(matcher, options=nil) #:nodoc:
|
||||
@data.delete_if { |k,v| k =~ matcher }
|
||||
end
|
||||
private
|
||||
def cache_configured?
|
||||
self.class.cache_configured?
|
||||
end
|
||||
|
||||
module ThreadSafety #:nodoc:
|
||||
def read(name, options=nil) #:nodoc:
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def write(name, value, options=nil) #:nodoc:
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def delete(name, options=nil) #:nodoc:
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def delete_matched(matcher, options=nil) #:nodoc:
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
end
|
||||
|
||||
class MemoryStore < UnthreadedMemoryStore #:nodoc:
|
||||
def initialize #:nodoc:
|
||||
super
|
||||
if ActionController::Base.allow_concurrency
|
||||
@mutex = Mutex.new
|
||||
MemoryStore.module_eval { include ThreadSafety }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DRbStore < MemoryStore #:nodoc:
|
||||
attr_reader :address
|
||||
|
||||
def initialize(address = 'druby://localhost:9192')
|
||||
super()
|
||||
@address = address
|
||||
@data = DRbObject.new(nil, address)
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
require_library_or_gem 'memcache'
|
||||
class MemCacheStore < MemoryStore #:nodoc:
|
||||
attr_reader :addresses
|
||||
|
||||
def initialize(*addresses)
|
||||
super()
|
||||
addresses = addresses.flatten
|
||||
addresses = ["localhost"] if addresses.empty?
|
||||
@addresses = addresses
|
||||
@data = MemCache.new(*addresses)
|
||||
end
|
||||
end
|
||||
rescue LoadError
|
||||
# MemCache wasn't available so neither can the store be
|
||||
end
|
||||
|
||||
class UnthreadedFileStore #:nodoc:
|
||||
attr_reader :cache_path
|
||||
|
||||
def initialize(cache_path)
|
||||
@cache_path = cache_path
|
||||
end
|
||||
|
||||
def write(name, value, options = nil) #:nodoc:
|
||||
ensure_cache_path(File.dirname(real_file_path(name)))
|
||||
File.open(real_file_path(name), "wb+") { |f| f.write(value) }
|
||||
rescue => e
|
||||
Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger
|
||||
end
|
||||
|
||||
def read(name, options = nil) #:nodoc:
|
||||
File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil
|
||||
end
|
||||
|
||||
def delete(name, options) #:nodoc:
|
||||
File.delete(real_file_path(name))
|
||||
rescue SystemCallError => e
|
||||
# If there's no cache, then there's nothing to complain about
|
||||
end
|
||||
|
||||
def delete_matched(matcher, options) #:nodoc:
|
||||
search_dir(@cache_path) do |f|
|
||||
if f =~ matcher
|
||||
begin
|
||||
File.delete(f)
|
||||
rescue SystemCallError => e
|
||||
# If there's no cache, then there's nothing to complain about
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def real_file_path(name)
|
||||
'%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')]
|
||||
end
|
||||
|
||||
def ensure_cache_path(path)
|
||||
FileUtils.makedirs(path) unless File.exist?(path)
|
||||
end
|
||||
|
||||
def search_dir(dir, &callback)
|
||||
Dir.foreach(dir) do |d|
|
||||
next if d == "." || d == ".."
|
||||
name = File.join(dir, d)
|
||||
if File.directory?(name)
|
||||
search_dir(name, &callback)
|
||||
else
|
||||
callback.call name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FileStore < UnthreadedFileStore #:nodoc:
|
||||
def initialize(cache_path)
|
||||
super(cache_path)
|
||||
if ActionController::Base.allow_concurrency
|
||||
@mutex = Mutex.new
|
||||
FileStore.module_eval { include ThreadSafety }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
|
||||
# They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
|
||||
#
|
||||
# class ListSweeper < ActionController::Caching::Sweeper
|
||||
# observe List, Item
|
||||
#
|
||||
# def after_save(record)
|
||||
# list = record.is_a?(List) ? record : record.list
|
||||
# expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
|
||||
# expire_action(:controller => "lists", :action => "all")
|
||||
# list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The sweeper is assigned in the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# caches_action :index, :show, :public, :feed
|
||||
# cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
|
||||
# end
|
||||
#
|
||||
# In the example above, four actions are cached and three actions are responsible for expiring those caches.
|
||||
module Sweeping
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
def cache_sweeper(*sweepers)
|
||||
return unless perform_caching
|
||||
configuration = sweepers.extract_options!
|
||||
sweepers.each do |sweeper|
|
||||
ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
|
||||
sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance
|
||||
|
||||
if sweeper_instance.is_a?(Sweeper)
|
||||
around_filter(sweeper_instance, :only => configuration[:only])
|
||||
else
|
||||
after_filter(sweeper_instance, :only => configuration[:only])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
|
||||
class Sweeper < ActiveRecord::Observer #:nodoc:
|
||||
attr_accessor :controller
|
||||
|
||||
def before(controller)
|
||||
self.controller = controller
|
||||
callback(:before)
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
callback(:after)
|
||||
# Clean up, so that the controller can be collected after this request
|
||||
self.controller = nil
|
||||
end
|
||||
|
||||
protected
|
||||
# gets the action cache path for the given options.
|
||||
def action_path_for(options)
|
||||
ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
|
||||
end
|
||||
|
||||
# Retrieve instance variables set in the controller.
|
||||
def assigns(key)
|
||||
controller.instance_variable_get("@#{key}")
|
||||
end
|
||||
|
||||
private
|
||||
def callback(timing)
|
||||
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
|
||||
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
|
||||
|
||||
send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
|
||||
send!(action_callback_method_name) if respond_to?(action_callback_method_name, true)
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments)
|
||||
return if @controller.nil?
|
||||
@controller.send!(method, *arguments)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module SqlCache
|
||||
def self.included(base) #:nodoc:
|
||||
if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache)
|
||||
base.alias_method_chain :perform_action, :caching
|
||||
end
|
||||
end
|
||||
|
||||
def perform_action_with_caching
|
||||
ActiveRecord::Base.cache do
|
||||
perform_action_without_caching
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
143
vendor/rails/actionpack/lib/action_controller/caching/actions.rb
vendored
Normal file
143
vendor/rails/actionpack/lib/action_controller/caching/actions.rb
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
require 'set'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
|
||||
# every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which
|
||||
# allows for authentication and other restrictions on whether someone is allowed to see the cache. Example:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :show, :feed
|
||||
# end
|
||||
#
|
||||
# In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
|
||||
# show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
|
||||
#
|
||||
# Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both
|
||||
# the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
|
||||
# "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
|
||||
# "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
|
||||
#
|
||||
# Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt>
|
||||
# are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same
|
||||
# as <tt>:action => 'list', :format => :xml</tt>.
|
||||
#
|
||||
# You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
|
||||
# for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# before_filter :authenticate, :except => :public
|
||||
# caches_page :public
|
||||
# caches_action :show, :cache_path => { :project => 1 }
|
||||
# caches_action :show, :cache_path => Proc.new { |controller|
|
||||
# controller.params[:user_id] ?
|
||||
# controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
|
||||
# controller.send(:list_url, c.params[:id]) }
|
||||
# end
|
||||
module Actions
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
attr_accessor :rendered_action_cache, :action_cache_path
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Declares that +actions+ should be cached.
|
||||
# See ActionController::Caching::Actions for details.
|
||||
def caches_action(*actions)
|
||||
return unless cache_configured?
|
||||
around_filter(ActionCacheFilter.new(*actions))
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def expire_action(options = {})
|
||||
return unless cache_configured?
|
||||
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action })))
|
||||
end
|
||||
else
|
||||
expire_fragment(ActionCachePath.path_for(self, options))
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCacheFilter #:nodoc:
|
||||
def initialize(*actions, &block)
|
||||
@options = actions.extract_options!
|
||||
@actions = Set.new(actions)
|
||||
end
|
||||
|
||||
def before(controller)
|
||||
return unless @actions.include?(controller.action_name.intern)
|
||||
|
||||
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
|
||||
|
||||
if cache = controller.read_fragment(cache_path.path)
|
||||
controller.rendered_action_cache = true
|
||||
set_content_type!(controller, cache_path.extension)
|
||||
controller.send!(:render_for_text, cache)
|
||||
false
|
||||
else
|
||||
controller.action_cache_path = cache_path
|
||||
end
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller)
|
||||
controller.write_fragment(controller.action_cache_path.path, controller.response.body)
|
||||
end
|
||||
|
||||
private
|
||||
def set_content_type!(controller, extension)
|
||||
controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension
|
||||
end
|
||||
|
||||
def path_options_for(controller, options)
|
||||
((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {}
|
||||
end
|
||||
|
||||
def caching_allowed(controller)
|
||||
controller.request.get? && controller.response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
|
||||
class ActionCachePath
|
||||
attr_reader :path, :extension
|
||||
|
||||
class << self
|
||||
def path_for(controller, options)
|
||||
new(controller, options).path
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(controller, options = {})
|
||||
@extension = extract_extension(controller.request.path)
|
||||
path = controller.url_for(options).split('://').last
|
||||
normalize!(path)
|
||||
add_extension!(path, @extension)
|
||||
@path = URI.unescape(path)
|
||||
end
|
||||
|
||||
private
|
||||
def normalize!(path)
|
||||
path << 'index' if path[-1] == ?/
|
||||
end
|
||||
|
||||
def add_extension!(path, extension)
|
||||
path << ".#{extension}" if extension
|
||||
end
|
||||
|
||||
def extract_extension(file_path)
|
||||
# Don't want just what comes after the last '.' to accommodate multi part extensions
|
||||
# such as tar.gz.
|
||||
file_path[/^[^.]+\.(.+)$/, 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
127
vendor/rails/actionpack/lib/action_controller/caching/fragments.rb
vendored
Normal file
127
vendor/rails/actionpack/lib/action_controller/caching/fragments.rb
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
|
||||
# certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
|
||||
# parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like:
|
||||
#
|
||||
# <b>Hello <%= @name %></b>
|
||||
# <% cache do %>
|
||||
# All the topics in the system:
|
||||
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
|
||||
# <% end %>
|
||||
#
|
||||
# This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would
|
||||
# be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.
|
||||
#
|
||||
# This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using
|
||||
# <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like:
|
||||
#
|
||||
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
|
||||
#
|
||||
# That would result in a name such as "/topics/list/all_topics", avoiding conflicts with the action cache and with any fragments that use a
|
||||
# different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique
|
||||
# cache names that we can refer to when we need to expire the cache.
|
||||
#
|
||||
# The expiration call for this example is:
|
||||
#
|
||||
# expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
|
||||
module Fragments
|
||||
def self.included(base) #:nodoc:
|
||||
base.class_eval do
|
||||
class << self
|
||||
def fragment_cache_store=(store_option) #:nodoc:
|
||||
ActiveSupport::Deprecation.warn('The fragment_cache_store= method is now use cache_store=')
|
||||
self.cache_store = store_option
|
||||
end
|
||||
|
||||
def fragment_cache_store #:nodoc:
|
||||
ActiveSupport::Deprecation.warn('The fragment_cache_store method is now use cache_store')
|
||||
cache_store
|
||||
end
|
||||
end
|
||||
|
||||
def fragment_cache_store=(store_option) #:nodoc:
|
||||
ActiveSupport::Deprecation.warn('The fragment_cache_store= method is now use cache_store=')
|
||||
self.cache_store = store_option
|
||||
end
|
||||
|
||||
def fragment_cache_store #:nodoc:
|
||||
ActiveSupport::Deprecation.warn('The fragment_cache_store method is now use cache_store')
|
||||
cache_store
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Given a key (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
|
||||
# writing, or expiring a cached fragment. If the key is a hash, the generated key is the return
|
||||
# value of url_for on that hash (without the protocol). All keys are prefixed with "views/" and uses
|
||||
# ActiveSupport::Cache.expand_cache_key for the expansion.
|
||||
def fragment_cache_key(key)
|
||||
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
|
||||
end
|
||||
|
||||
def fragment_for(block, name = {}, options = nil) #:nodoc:
|
||||
unless perform_caching then block.call; return end
|
||||
|
||||
buffer = yield
|
||||
|
||||
if cache = read_fragment(name, options)
|
||||
buffer.concat(cache)
|
||||
else
|
||||
pos = buffer.length
|
||||
block.call
|
||||
write_fragment(name, buffer[pos..-1], options)
|
||||
end
|
||||
end
|
||||
|
||||
# Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def write_fragment(key, content, options = nil)
|
||||
return unless cache_configured?
|
||||
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
self.class.benchmark "Cached fragment miss: #{key}" do
|
||||
cache_store.write(key, content, options)
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
# Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
||||
def read_fragment(key, options = nil)
|
||||
return unless cache_configured?
|
||||
|
||||
key = fragment_cache_key(key)
|
||||
|
||||
self.class.benchmark "Cached fragment hit: #{key}" do
|
||||
cache_store.read(key, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Name can take one of three forms:
|
||||
# * String: This would normally take the form of a path like "pages/45/notes"
|
||||
# * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
|
||||
# * Regexp: Will destroy all the matched fragments, example:
|
||||
# %r{pages/\d*/notes}
|
||||
# Ensure you do not specify start and finish in the regex (^$) because
|
||||
# the actual filename matched looks like ./cache/filename/path.cache
|
||||
# Regexp expiration is only supported on caches that can iterate over
|
||||
# all keys (unlike memcached).
|
||||
def expire_fragment(key, options = nil)
|
||||
return unless cache_configured?
|
||||
|
||||
key = key.is_a?(Regexp) ? key : fragment_cache_key(key)
|
||||
|
||||
if key.is_a?(Regexp)
|
||||
self.class.benchmark "Expired fragments matching: #{key.source}" do
|
||||
cache_store.delete_matched(key, options)
|
||||
end
|
||||
else
|
||||
self.class.benchmark "Expired fragment: #{key}" do
|
||||
cache_store.delete(key, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
154
vendor/rails/actionpack/lib/action_controller/caching/pages.rb
vendored
Normal file
154
vendor/rails/actionpack/lib/action_controller/caching/pages.rb
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
require 'fileutils'
|
||||
require 'uri'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
|
||||
# can serve without going through Action Pack. This is the fastest way to cache your content as opposed to going dynamically
|
||||
# through the process of generating the content. Unfortunately, this incredible speed-up is only available to stateless pages
|
||||
# where all visitors are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are
|
||||
# a great fit for this approach, but account-based systems where people log in and manipulate their own data are often less
|
||||
# likely candidates.
|
||||
#
|
||||
# Specifying which actions to cache is done through the <tt>caches_page</tt> class method:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# caches_page :show, :new
|
||||
# end
|
||||
#
|
||||
# This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>,
|
||||
# which match the URLs used to trigger the dynamic generation. This is how the web server is able
|
||||
# pick up a cache file when it exists and otherwise let the request pass on to Action Pack to generate it.
|
||||
#
|
||||
# Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
|
||||
# is not restored before another hit is made against it. The API for doing so mimics the options from +url_for+ and friends:
|
||||
#
|
||||
# class WeblogController < ActionController::Base
|
||||
# def update
|
||||
# List.update(params[:list][:id], params[:list])
|
||||
# expire_page :action => "show", :id => params[:list][:id]
|
||||
# redirect_to :action => "show", :id => params[:list][:id]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
|
||||
# expired.
|
||||
#
|
||||
# == Setting the cache directory
|
||||
#
|
||||
# The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
|
||||
# For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing
|
||||
# this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
|
||||
# web server to look in the new location for cached files.
|
||||
#
|
||||
# == Setting the cache extension
|
||||
#
|
||||
# Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
|
||||
# order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
|
||||
# If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
|
||||
# extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
|
||||
module Pages
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval do
|
||||
@@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
|
||||
cattr_accessor :page_cache_directory
|
||||
|
||||
@@page_cache_extension = '.html'
|
||||
cattr_accessor :page_cache_extension
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Expires the page that was cached with the +path+ as a key. Example:
|
||||
# expire_page "/lists/show"
|
||||
def expire_page(path)
|
||||
return unless perform_caching
|
||||
|
||||
benchmark "Expired page: #{page_cache_file(path)}" do
|
||||
File.delete(page_cache_path(path)) if File.exist?(page_cache_path(path))
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +path+. Example:
|
||||
# cache_page "I'm the cached content", "/lists/show"
|
||||
def cache_page(content, path)
|
||||
return unless perform_caching
|
||||
|
||||
benchmark "Cached page: #{page_cache_file(path)}" do
|
||||
FileUtils.makedirs(File.dirname(page_cache_path(path)))
|
||||
File.open(page_cache_path(path), "wb+") { |f| f.write(content) }
|
||||
end
|
||||
end
|
||||
|
||||
# Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
|
||||
# matches the triggering url.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # cache the index action
|
||||
# caches_page :index
|
||||
#
|
||||
# # cache the index action except for JSON requests
|
||||
# caches_page :index, :if => Proc.new { |c| !c.request.format.json? }
|
||||
def caches_page(*actions)
|
||||
return unless perform_caching
|
||||
options = actions.extract_options!
|
||||
after_filter({:only => actions}.merge(options)) { |c| c.cache_page }
|
||||
end
|
||||
|
||||
private
|
||||
def page_cache_file(path)
|
||||
name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/'))
|
||||
name << page_cache_extension unless (name.split('/').last || name).include? '.'
|
||||
return name
|
||||
end
|
||||
|
||||
def page_cache_path(path)
|
||||
page_cache_directory + page_cache_file(path)
|
||||
end
|
||||
end
|
||||
|
||||
# Expires the page that was cached with the +options+ as a key. Example:
|
||||
# expire_page :controller => "lists", :action => "show"
|
||||
def expire_page(options = {})
|
||||
return unless perform_caching
|
||||
|
||||
if options.is_a?(Hash)
|
||||
if options[:action].is_a?(Array)
|
||||
options[:action].dup.each do |action|
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action)))
|
||||
end
|
||||
else
|
||||
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
|
||||
end
|
||||
else
|
||||
self.class.expire_page(options)
|
||||
end
|
||||
end
|
||||
|
||||
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used
|
||||
# If no options are provided, the requested url is used. Example:
|
||||
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
|
||||
def cache_page(content = nil, options = nil)
|
||||
return unless perform_caching && caching_allowed
|
||||
|
||||
path = case options
|
||||
when Hash
|
||||
url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format]))
|
||||
when String
|
||||
options
|
||||
else
|
||||
request.path
|
||||
end
|
||||
|
||||
self.class.cache_page(content || response.body, path)
|
||||
end
|
||||
|
||||
private
|
||||
def caching_allowed
|
||||
request.get? && response.headers['Status'].to_i == 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
vendor/rails/actionpack/lib/action_controller/caching/sql_cache.rb
vendored
Normal file
18
vendor/rails/actionpack/lib/action_controller/caching/sql_cache.rb
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
module SqlCache
|
||||
def self.included(base) #:nodoc:
|
||||
if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache)
|
||||
base.alias_method_chain :perform_action, :caching
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def perform_action_with_caching
|
||||
ActiveRecord::Base.cache do
|
||||
perform_action_without_caching
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
97
vendor/rails/actionpack/lib/action_controller/caching/sweeping.rb
vendored
Normal file
97
vendor/rails/actionpack/lib/action_controller/caching/sweeping.rb
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
# Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
|
||||
# They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
|
||||
#
|
||||
# class ListSweeper < ActionController::Caching::Sweeper
|
||||
# observe List, Item
|
||||
#
|
||||
# def after_save(record)
|
||||
# list = record.is_a?(List) ? record : record.list
|
||||
# expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
|
||||
# expire_action(:controller => "lists", :action => "all")
|
||||
# list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The sweeper is assigned in the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# caches_action :index, :show, :public, :feed
|
||||
# cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
|
||||
# end
|
||||
#
|
||||
# In the example above, four actions are cached and three actions are responsible for expiring those caches.
|
||||
#
|
||||
# You can also name an explicit class in the declaration of a sweeper, which is needed if the sweeper is in a module:
|
||||
#
|
||||
# class ListsController < ApplicationController
|
||||
# caches_action :index, :show, :public, :feed
|
||||
# cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ]
|
||||
# end
|
||||
module Sweeping
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
def cache_sweeper(*sweepers)
|
||||
configuration = sweepers.extract_options!
|
||||
|
||||
sweepers.each do |sweeper|
|
||||
ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
|
||||
sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(Inflector.classify(sweeper)) : sweeper).instance
|
||||
|
||||
if sweeper_instance.is_a?(Sweeper)
|
||||
around_filter(sweeper_instance, :only => configuration[:only])
|
||||
else
|
||||
after_filter(sweeper_instance, :only => configuration[:only])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
|
||||
class Sweeper < ActiveRecord::Observer #:nodoc:
|
||||
attr_accessor :controller
|
||||
|
||||
def before(controller)
|
||||
self.controller = controller
|
||||
callback(:before) if controller.perform_caching
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
callback(:after) if controller.perform_caching
|
||||
# Clean up, so that the controller can be collected after this request
|
||||
self.controller = nil
|
||||
end
|
||||
|
||||
protected
|
||||
# gets the action cache path for the given options.
|
||||
def action_path_for(options)
|
||||
ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
|
||||
end
|
||||
|
||||
# Retrieve instance variables set in the controller.
|
||||
def assigns(key)
|
||||
controller.instance_variable_get("@#{key}")
|
||||
end
|
||||
|
||||
private
|
||||
def callback(timing)
|
||||
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
|
||||
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
|
||||
|
||||
send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
|
||||
send!(action_callback_method_name) if respond_to?(action_callback_method_name, true)
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments)
|
||||
return if @controller.nil?
|
||||
@controller.send!(method, *arguments)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -89,13 +89,12 @@ class CGI #:nodoc:
|
|||
cookies = Hash.new([])
|
||||
|
||||
if raw_cookie
|
||||
raw_cookie.split(/[;,]\s?/).each do |pairs|
|
||||
name, values = pairs.split('=',2)
|
||||
next unless name and values
|
||||
raw_cookie.split(/;\s?/).each do |pairs|
|
||||
name, value = pairs.split('=',2)
|
||||
next unless name and value
|
||||
name = CGI::unescape(name)
|
||||
values = values.split('&').collect!{|v| CGI::unescape(v) }
|
||||
unless cookies.has_key?(name)
|
||||
cookies[name] = new(name, *values)
|
||||
cookies[name] = new(name, CGI::unescape(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ require 'action_controller/session/cookie_store'
|
|||
|
||||
module ActionController #:nodoc:
|
||||
class Base
|
||||
# Process a request extracted from an CGI object and return a response. Pass false as <tt>session_options</tt> to disable
|
||||
# Process a request extracted from a CGI object and return a response. Pass false as <tt>session_options</tt> to disable
|
||||
# sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
|
||||
#
|
||||
# * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
|
||||
|
@ -17,7 +17,7 @@ module ActionController #:nodoc:
|
|||
# an ArgumentError is raised.
|
||||
# * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object. If not set, the session will continue
|
||||
# indefinitely.
|
||||
# * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
|
||||
# * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
|
||||
# server.
|
||||
# * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
|
||||
# * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
|
||||
|
@ -34,7 +34,8 @@ module ActionController #:nodoc:
|
|||
|
||||
class CgiRequest < AbstractRequest #:nodoc:
|
||||
attr_accessor :cgi, :session_options
|
||||
class SessionFixationAttempt < StandardError; end #:nodoc:
|
||||
class SessionFixationAttempt < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
DEFAULT_SESSION_OPTIONS = {
|
||||
:database_manager => CGI::Session::CookieStore, # store data in cookie
|
||||
|
|
|
@ -39,12 +39,7 @@ module ActionController #:nodoc:
|
|||
base.class_eval do
|
||||
include InstanceMethods
|
||||
extend ClassMethods
|
||||
|
||||
helper do
|
||||
def render_component(options)
|
||||
@controller.send!(:render_component_as_string, options)
|
||||
end
|
||||
end
|
||||
helper HelperMethods
|
||||
|
||||
# If this controller was instantiated to process a component request,
|
||||
# +parent_controller+ points to the instantiator of this controller.
|
||||
|
@ -67,6 +62,12 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
module HelperMethods
|
||||
def render_component(options)
|
||||
@controller.send!(:render_component_as_string, options)
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
# Extracts the action_name from the request parameters and performs that action.
|
||||
def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
|
|
|
@ -1,31 +1,38 @@
|
|||
module ActionController #:nodoc:
|
||||
# Cookies are read and written through ActionController#cookies. The cookies being read are what were received along with the request,
|
||||
# the cookies being written are what will be sent out with the response. Cookies are read by value (so you won't get the cookie object
|
||||
# itself back -- just the value it holds). Examples for writing:
|
||||
# Cookies are read and written through ActionController#cookies.
|
||||
#
|
||||
# cookies[:user_name] = "david" # => Will set a simple session cookie
|
||||
# The cookies being read are the ones received along with the request, the cookies
|
||||
# being written will be sent out with the response. Reading a cookie does not get
|
||||
# the cookie object itself back, just the value it holds.
|
||||
#
|
||||
# Examples for writing:
|
||||
#
|
||||
# # Sets a simple session cookie.
|
||||
# cookies[:user_name] = "david"
|
||||
#
|
||||
# # Sets a cookie that expires in 1 hour.
|
||||
# cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
|
||||
# # => Will set a cookie that expires in 1 hour
|
||||
#
|
||||
# Examples for reading:
|
||||
#
|
||||
# cookies[:user_name] # => "david"
|
||||
# cookies.size # => 2
|
||||
# cookies.size # => 2
|
||||
#
|
||||
# Example for deleting:
|
||||
#
|
||||
# cookies.delete :user_name
|
||||
#
|
||||
# All the option symbols for setting cookies are:
|
||||
# The option symbols for setting cookies are:
|
||||
#
|
||||
# * <tt>value</tt> - the cookie's value or list of values (as an array).
|
||||
# * <tt>path</tt> - the path for which this cookie applies. Defaults to the root of the application.
|
||||
# * <tt>domain</tt> - the domain for which this cookie applies.
|
||||
# * <tt>expires</tt> - the time at which this cookie expires, as a +Time+ object.
|
||||
# * <tt>secure</tt> - whether this cookie is a secure cookie or not (default to false).
|
||||
# Secure cookies are only transmitted to HTTPS servers.
|
||||
# * <tt>http_only</tt> - whether this cookie is accessible via scripting or only HTTP (defaults to false).
|
||||
|
||||
# * <tt>:value</tt> - The cookie's value or list of values (as an array).
|
||||
# * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
|
||||
# of the application.
|
||||
# * <tt>:domain</tt> - The domain for which this cookie applies.
|
||||
# * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
|
||||
# * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
|
||||
# Default is +false+.
|
||||
# * <tt>:http_only</tt> - Whether this cookie is accessible via scripting or
|
||||
# only HTTP. Defaults to +false+.
|
||||
module Cookies
|
||||
def self.included(base)
|
||||
base.helper_method :cookies
|
||||
|
@ -45,8 +52,7 @@ module ActionController #:nodoc:
|
|||
update(@cookies)
|
||||
end
|
||||
|
||||
# Returns the value of the cookie by +name+ -- or nil if no such cookie exists. You set new cookies using cookies[]=
|
||||
# (for simple name/value cookies without options).
|
||||
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
|
||||
def [](name)
|
||||
cookie = @cookies[name.to_s]
|
||||
if cookie && cookie.respond_to?(:value)
|
||||
|
@ -54,6 +60,8 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Sets the cookie named +name+. The second argument may be the very cookie
|
||||
# value, or a hash of options as documented above.
|
||||
def []=(name, options)
|
||||
if options.is_a?(Hash)
|
||||
options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
|
||||
|
@ -66,14 +74,18 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
# Removes the cookie on the client machine by setting the value to an empty string
|
||||
# and setting its expiration date into the past. Like []=, you can pass in an options
|
||||
# hash to delete cookies with extra data such as a +path+.
|
||||
# and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
|
||||
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
|
||||
def delete(name, options = {})
|
||||
options.stringify_keys!
|
||||
set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0)))
|
||||
end
|
||||
|
||||
private
|
||||
# Builds a CGI::Cookie object and adds the cookie to the response headers.
|
||||
#
|
||||
# The path of the cookie defaults to "/" if there's none in +options+, and
|
||||
# everything is passed to the CGI::Cookie constructor.
|
||||
def set_cookie(options) #:doc:
|
||||
options["path"] = "/" unless options["path"]
|
||||
cookie = CGI::Cookie.new(options)
|
||||
|
|
|
@ -2,27 +2,39 @@ module ActionController
|
|||
# Dispatches requests to the appropriate controller and takes care of
|
||||
# reloading the app after each request when Dependencies.load? is true.
|
||||
class Dispatcher
|
||||
@@guard = Mutex.new
|
||||
|
||||
class << self
|
||||
def define_dispatcher_callbacks(cache_classes)
|
||||
unless cache_classes
|
||||
# Development mode callbacks
|
||||
before_dispatch :reload_application
|
||||
after_dispatch :cleanup_application
|
||||
end
|
||||
|
||||
# Common callbacks
|
||||
to_prepare :load_application_controller do
|
||||
begin
|
||||
require_dependency 'application' unless defined?(::ApplicationController)
|
||||
rescue LoadError => error
|
||||
raise unless error.message =~ /application\.rb/
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord)
|
||||
before_dispatch { ActiveRecord::Base.verify_active_connections! }
|
||||
to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers }
|
||||
end
|
||||
|
||||
after_dispatch :flush_logger if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush)
|
||||
end
|
||||
|
||||
# Backward-compatible class method takes CGI-specific args. Deprecated
|
||||
# in favor of Dispatcher.new(output, request, response).dispatch.
|
||||
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
|
||||
new(output).dispatch_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
# Declare a block to be called before each dispatch.
|
||||
# Run in the order declared.
|
||||
def before_dispatch(*method_names, &block)
|
||||
callbacks[:before].concat method_names
|
||||
callbacks[:before] << block if block_given?
|
||||
end
|
||||
|
||||
# Declare a block to be called after each dispatch.
|
||||
# Run in reverse of the order declared.
|
||||
def after_dispatch(*method_names, &block)
|
||||
callbacks[:after].concat method_names
|
||||
callbacks[:after] << block if block_given?
|
||||
end
|
||||
|
||||
# Add a preparation callback. Preparation callbacks are run before every
|
||||
# request in development mode, and before the first request in production
|
||||
# mode.
|
||||
|
@ -32,16 +44,9 @@ module ActionController
|
|||
# existing callback. Passing an identifier is a suggested practice if the
|
||||
# code adding a preparation block may be reloaded.
|
||||
def to_prepare(identifier = nil, &block)
|
||||
# Already registered: update the existing callback
|
||||
if identifier
|
||||
if callback = callbacks[:prepare].assoc(identifier)
|
||||
callback[1] = block
|
||||
else
|
||||
callbacks[:prepare] << [identifier, block]
|
||||
end
|
||||
else
|
||||
callbacks[:prepare] << block
|
||||
end
|
||||
@prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new
|
||||
callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
|
||||
@prepare_dispatch_callbacks | callback
|
||||
end
|
||||
|
||||
# If the block raises, send status code as a last-ditch response.
|
||||
|
@ -86,37 +91,26 @@ module ActionController
|
|||
end
|
||||
|
||||
cattr_accessor :error_file_path
|
||||
self.error_file_path = "#{::RAILS_ROOT}/public" if defined? ::RAILS_ROOT
|
||||
self.error_file_path = Rails.public_path if defined?(Rails.public_path)
|
||||
|
||||
cattr_accessor :callbacks
|
||||
self.callbacks = Hash.new { |h, k| h[k] = [] }
|
||||
|
||||
cattr_accessor :unprepared
|
||||
self.unprepared = true
|
||||
|
||||
|
||||
before_dispatch :reload_application
|
||||
before_dispatch :prepare_application
|
||||
after_dispatch :flush_logger
|
||||
after_dispatch :cleanup_application
|
||||
|
||||
if defined? ActiveRecord
|
||||
to_prepare :activerecord_instantiate_observers do
|
||||
ActiveRecord::Base.instantiate_observers
|
||||
end
|
||||
end
|
||||
include ActiveSupport::Callbacks
|
||||
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
||||
|
||||
def initialize(output, request = nil, response = nil)
|
||||
@output, @request, @response = output, request, response
|
||||
end
|
||||
|
||||
def dispatch
|
||||
run_callbacks :before
|
||||
handle_request
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
ensure
|
||||
run_callbacks :after, :reverse_each
|
||||
@@guard.synchronize do
|
||||
begin
|
||||
run_callbacks :before_dispatch
|
||||
handle_request
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
ensure
|
||||
run_callbacks :after_dispatch, :enumerator => :reverse_each
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch_cgi(cgi, session_options)
|
||||
|
@ -130,39 +124,23 @@ module ActionController
|
|||
end
|
||||
|
||||
def reload_application
|
||||
if Dependencies.load?
|
||||
Routing::Routes.reload
|
||||
self.unprepared = true
|
||||
end
|
||||
end
|
||||
# Run prepare callbacks before every request in development mode
|
||||
run_callbacks :prepare_dispatch
|
||||
|
||||
def prepare_application(force = false)
|
||||
begin
|
||||
require_dependency 'application' unless defined?(::ApplicationController)
|
||||
rescue LoadError => error
|
||||
raise unless error.message =~ /application\.rb/
|
||||
end
|
||||
|
||||
ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord)
|
||||
|
||||
if unprepared || force
|
||||
run_callbacks :prepare
|
||||
self.unprepared = false
|
||||
end
|
||||
Routing::Routes.reload
|
||||
ActionView::TemplateFinder.reload! unless ActionView::Base.cache_template_loading
|
||||
end
|
||||
|
||||
# Cleanup the application by clearing out loaded classes so they can
|
||||
# be reloaded on the next request without restarting the server.
|
||||
def cleanup_application(force = false)
|
||||
if Dependencies.load? || force
|
||||
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
|
||||
Dependencies.clear
|
||||
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
|
||||
end
|
||||
def cleanup_application
|
||||
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
|
||||
Dependencies.clear
|
||||
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
|
||||
end
|
||||
|
||||
def flush_logger
|
||||
RAILS_DEFAULT_LOGGER.flush if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush)
|
||||
RAILS_DEFAULT_LOGGER.flush
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -171,17 +149,6 @@ module ActionController
|
|||
@controller.process(@request, @response).out(@output)
|
||||
end
|
||||
|
||||
def run_callbacks(kind, enumerator = :each)
|
||||
callbacks[kind].send!(enumerator) do |callback|
|
||||
case callback
|
||||
when Proc; callback.call(self)
|
||||
when String, Symbol; send!(callback)
|
||||
when Array; callback[1].call(self)
|
||||
else raise ArgumentError, "Unrecognized callback #{callback.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def failsafe_rescue(exception)
|
||||
self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
|
||||
if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base
|
||||
|
|
|
@ -126,8 +126,8 @@ module ActionController #:nodoc:
|
|||
# end
|
||||
#
|
||||
# To use a filter object with around_filter, pass an object responding
|
||||
# to :filter or both :before and :after. With a filter method, yield to
|
||||
# the block as above:
|
||||
# to <tt>:filter</tt> or both <tt>:before</tt> and <tt>:after</tt>. With a
|
||||
# filter method, yield to the block as above:
|
||||
#
|
||||
# around_filter BenchmarkingFilter
|
||||
#
|
||||
|
@ -191,8 +191,9 @@ module ActionController #:nodoc:
|
|||
# == Filter conditions
|
||||
#
|
||||
# Filters may be limited to specific actions by declaring the actions to
|
||||
# include or exclude. Both options accept single actions (:only => :index)
|
||||
# or arrays of actions (:except => [:foo, :bar]).
|
||||
# include or exclude. Both options accept single actions
|
||||
# (<tt>:only => :index</tt>) or arrays of actions
|
||||
# (<tt>:except => [:foo, :bar]</tt>).
|
||||
#
|
||||
# class Journal < ActionController::Base
|
||||
# # Require authentication for edit and delete.
|
||||
|
@ -244,17 +245,212 @@ module ActionController #:nodoc:
|
|||
# filter and controller action will not be run. If #before renders or redirects,
|
||||
# the second half of #around and will still run but #after and the
|
||||
# action will not. If #around fails to yield, #after will not be run.
|
||||
|
||||
class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
|
||||
def append_filter_to_chain(filters, filter_type, &block)
|
||||
pos = find_filter_append_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def prepend_filter_to_chain(filters, filter_type, &block)
|
||||
pos = find_filter_prepend_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def create_filters(filters, filter_type, &block)
|
||||
filters, conditions = extract_options(filters, &block)
|
||||
filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
|
||||
filters
|
||||
end
|
||||
|
||||
def skip_filter_in_chain(*filters, &test)
|
||||
filters, conditions = extract_options(filters)
|
||||
filters.each do |filter|
|
||||
if callback = find(filter) then delete(callback) end
|
||||
end if conditions.empty?
|
||||
update_filter_in_chain(filters, :skip => conditions, &test)
|
||||
end
|
||||
|
||||
private
|
||||
def update_filter_chain(filters, filter_type, pos, &block)
|
||||
new_filters = create_filters(filters, filter_type, &block)
|
||||
insert(pos, new_filters).flatten!
|
||||
end
|
||||
|
||||
def find_filter_append_position(filters, filter_type)
|
||||
# appending an after filter puts it at the end of the call chain
|
||||
# before and around filters go before the first after filter in the chain
|
||||
unless filter_type == :after
|
||||
each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
def find_filter_prepend_position(filters, filter_type)
|
||||
# prepending a before or around filter puts it at the front of the call chain
|
||||
# after filters go before the first after filter in the chain
|
||||
if filter_type == :after
|
||||
each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
return -1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
def find_or_create_filter(filter, filter_type, options = {})
|
||||
update_filter_in_chain([filter], options)
|
||||
|
||||
if found_filter = find(filter) { |f| f.type == filter_type }
|
||||
found_filter
|
||||
else
|
||||
filter_kind = case
|
||||
when filter.respond_to?(:before) && filter_type == :before
|
||||
:before
|
||||
when filter.respond_to?(:after) && filter_type == :after
|
||||
:after
|
||||
else
|
||||
:filter
|
||||
end
|
||||
|
||||
case filter_type
|
||||
when :before
|
||||
BeforeFilter.new(filter_kind, filter, options)
|
||||
when :after
|
||||
AfterFilter.new(filter_kind, filter, options)
|
||||
else
|
||||
AroundFilter.new(filter_kind, filter, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_filter_in_chain(filters, options, &test)
|
||||
filters.map! { |f| block_given? ? find(f, &test) : find(f) }
|
||||
filters.compact!
|
||||
|
||||
map! do |filter|
|
||||
if filters.include?(filter)
|
||||
new_filter = filter.dup
|
||||
new_filter.options.merge!(options)
|
||||
new_filter
|
||||
else
|
||||
filter
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
|
||||
def before?
|
||||
self.class == BeforeFilter
|
||||
end
|
||||
|
||||
def after?
|
||||
self.class == AfterFilter
|
||||
end
|
||||
|
||||
def around?
|
||||
self.class == AroundFilter
|
||||
end
|
||||
|
||||
private
|
||||
def should_not_skip?(controller)
|
||||
if options[:skip]
|
||||
!included_in_action?(controller, options[:skip])
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def included_in_action?(controller, options)
|
||||
if options[:only]
|
||||
Array(options[:only]).map(&:to_s).include?(controller.action_name)
|
||||
elsif options[:except]
|
||||
!Array(options[:except]).map(&:to_s).include?(controller.action_name)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def should_run_callback?(controller)
|
||||
should_not_skip?(controller) && included_in_action?(controller, options) && super
|
||||
end
|
||||
end
|
||||
|
||||
class AroundFilter < Filter #:nodoc:
|
||||
def type
|
||||
:around
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
if should_run_callback?(controller)
|
||||
method = filter_responds_to_before_and_after? ? around_proc : self.method
|
||||
|
||||
# For around_filter do |controller, action|
|
||||
if method.is_a?(Proc) && method.arity == 2
|
||||
evaluate_method(method, controller, block)
|
||||
else
|
||||
evaluate_method(method, controller, &block)
|
||||
end
|
||||
else
|
||||
block.call
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def filter_responds_to_before_and_after?
|
||||
method.respond_to?(:before) && method.respond_to?(:after)
|
||||
end
|
||||
|
||||
def around_proc
|
||||
Proc.new do |controller, action|
|
||||
method.before(controller)
|
||||
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
|
||||
else
|
||||
begin
|
||||
action.call
|
||||
ensure
|
||||
method.after(controller)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class BeforeFilter < Filter #:nodoc:
|
||||
def type
|
||||
:before
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
super
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AfterFilter < Filter #:nodoc:
|
||||
def type
|
||||
:after
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# The passed <tt>filters</tt> will be appended to the filter_chain and
|
||||
# will execute before the action on this controller is performed.
|
||||
def append_before_filter(*filters, &block)
|
||||
append_filter_to_chain(filters, :before, &block)
|
||||
filter_chain.append_filter_to_chain(filters, :before, &block)
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will be prepended to the filter_chain and
|
||||
# will execute before the action on this controller is performed.
|
||||
def prepend_before_filter(*filters, &block)
|
||||
prepend_filter_to_chain(filters, :before, &block)
|
||||
filter_chain.prepend_filter_to_chain(filters, :before, &block)
|
||||
end
|
||||
|
||||
# Shorthand for append_before_filter since it's the most common.
|
||||
|
@ -263,19 +459,18 @@ module ActionController #:nodoc:
|
|||
# The passed <tt>filters</tt> will be appended to the array of filters
|
||||
# that run _after_ actions on this controller are performed.
|
||||
def append_after_filter(*filters, &block)
|
||||
append_filter_to_chain(filters, :after, &block)
|
||||
filter_chain.append_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# The passed <tt>filters</tt> will be prepended to the array of filters
|
||||
# that run _after_ actions on this controller are performed.
|
||||
def prepend_after_filter(*filters, &block)
|
||||
prepend_filter_to_chain(filters, :after, &block)
|
||||
filter_chain.prepend_filter_to_chain(filters, :after, &block)
|
||||
end
|
||||
|
||||
# Shorthand for append_after_filter since it's the most common.
|
||||
alias :after_filter :append_after_filter
|
||||
|
||||
|
||||
# If you append_around_filter A.new, B.new, the filter chain looks like
|
||||
#
|
||||
# B#before
|
||||
|
@ -287,10 +482,7 @@ module ActionController #:nodoc:
|
|||
# With around filters which yield to the action block, #before and #after
|
||||
# are the code before and after the yield.
|
||||
def append_around_filter(*filters, &block)
|
||||
filters, conditions = extract_conditions(filters, &block)
|
||||
filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
|
||||
append_filter_to_chain([filter, conditions])
|
||||
end
|
||||
filter_chain.append_filter_to_chain(filters, :around, &block)
|
||||
end
|
||||
|
||||
# If you prepend_around_filter A.new, B.new, the filter chain looks like:
|
||||
|
@ -304,10 +496,7 @@ module ActionController #:nodoc:
|
|||
# With around filters which yield to the action block, #before and #after
|
||||
# are the code before and after the yield.
|
||||
def prepend_around_filter(*filters, &block)
|
||||
filters, conditions = extract_conditions(filters, &block)
|
||||
filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
|
||||
prepend_filter_to_chain([filter, conditions])
|
||||
end
|
||||
filter_chain.prepend_filter_to_chain(filters, :around, &block)
|
||||
end
|
||||
|
||||
# Shorthand for append_around_filter since it's the most common.
|
||||
|
@ -320,7 +509,7 @@ module ActionController #:nodoc:
|
|||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_before_filter(*filters)
|
||||
skip_filter_in_chain(*filters, &:before?)
|
||||
filter_chain.skip_filter_in_chain(*filters, &:before?)
|
||||
end
|
||||
|
||||
# Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
|
||||
|
@ -330,7 +519,7 @@ module ActionController #:nodoc:
|
|||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_after_filter(*filters)
|
||||
skip_filter_in_chain(*filters, &:after?)
|
||||
filter_chain.skip_filter_in_chain(*filters, &:after?)
|
||||
end
|
||||
|
||||
# Removes the specified filters from the filter chain. This only works for method reference (symbol)
|
||||
|
@ -340,334 +529,30 @@ module ActionController #:nodoc:
|
|||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||
# just like when you apply the filters.
|
||||
def skip_filter(*filters)
|
||||
skip_filter_in_chain(*filters)
|
||||
filter_chain.skip_filter_in_chain(*filters)
|
||||
end
|
||||
|
||||
# Returns an array of Filter objects for this controller.
|
||||
def filter_chain
|
||||
read_inheritable_attribute("filter_chain") || []
|
||||
if chain = read_inheritable_attribute('filter_chain')
|
||||
return chain
|
||||
else
|
||||
write_inheritable_attribute('filter_chain', FilterChain.new)
|
||||
return filter_chain
|
||||
end
|
||||
end
|
||||
|
||||
# Returns all the before filters for this class and all its ancestors.
|
||||
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
|
||||
def before_filters #:nodoc:
|
||||
filter_chain.select(&:before?).map(&:filter)
|
||||
filter_chain.select(&:before?).map(&:method)
|
||||
end
|
||||
|
||||
# Returns all the after filters for this class and all its ancestors.
|
||||
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
|
||||
def after_filters #:nodoc:
|
||||
filter_chain.select(&:after?).map(&:filter)
|
||||
filter_chain.select(&:after?).map(&:method)
|
||||
end
|
||||
|
||||
# Returns a mapping between filters and the actions that may run them.
|
||||
def included_actions #:nodoc:
|
||||
@included_actions ||= read_inheritable_attribute("included_actions") || {}
|
||||
end
|
||||
|
||||
# Returns a mapping between filters and actions that may not run them.
|
||||
def excluded_actions #:nodoc:
|
||||
@excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
|
||||
end
|
||||
|
||||
# Find a filter in the filter_chain where the filter method matches the _filter_ param
|
||||
# and (optionally) the passed block evaluates to true (mostly used for testing before?
|
||||
# and after? on the filter). Useful for symbol filters.
|
||||
#
|
||||
# The object of type Filter is passed to the block when yielded, not the filter itself.
|
||||
def find_filter(filter, &block) #:nodoc:
|
||||
filter_chain.select { |f| f.filter == filter && (!block_given? || yield(f)) }.first
|
||||
end
|
||||
|
||||
# Returns true if the filter is excluded from the given action
|
||||
def filter_excluded_from_action?(filter,action) #:nodoc:
|
||||
case
|
||||
when ia = included_actions[filter]
|
||||
!ia.include?(action)
|
||||
when ea = excluded_actions[filter]
|
||||
ea.include?(action)
|
||||
end
|
||||
end
|
||||
|
||||
# Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but
|
||||
# contains no logic for calling the actual filters.
|
||||
class Filter #:nodoc:
|
||||
attr_reader :filter, :included_actions, :excluded_actions
|
||||
|
||||
def initialize(filter)
|
||||
@filter = filter
|
||||
end
|
||||
|
||||
def type
|
||||
:around
|
||||
end
|
||||
|
||||
def before?
|
||||
type == :before
|
||||
end
|
||||
|
||||
def after?
|
||||
type == :after
|
||||
end
|
||||
|
||||
def around?
|
||||
type == :around
|
||||
end
|
||||
|
||||
def run(controller)
|
||||
raise ActionControllerError, 'No filter type: Nothing to do here.'
|
||||
end
|
||||
|
||||
def call(controller, &block)
|
||||
run(controller)
|
||||
end
|
||||
end
|
||||
|
||||
# Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old
|
||||
# before_filter and after_filter by moving the logic into the filter itself.
|
||||
class FilterProxy < Filter #:nodoc:
|
||||
def filter
|
||||
@filter.filter
|
||||
end
|
||||
end
|
||||
|
||||
class BeforeFilterProxy < FilterProxy #:nodoc:
|
||||
def type
|
||||
:before
|
||||
end
|
||||
|
||||
def run(controller)
|
||||
# only filters returning false are halted.
|
||||
@filter.call(controller)
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, @filter, :rendered_or_redirected)
|
||||
end
|
||||
end
|
||||
|
||||
def call(controller)
|
||||
yield unless run(controller)
|
||||
end
|
||||
end
|
||||
|
||||
class AfterFilterProxy < FilterProxy #:nodoc:
|
||||
def type
|
||||
:after
|
||||
end
|
||||
|
||||
def run(controller)
|
||||
@filter.call(controller)
|
||||
end
|
||||
|
||||
def call(controller)
|
||||
yield
|
||||
run(controller)
|
||||
end
|
||||
end
|
||||
|
||||
class SymbolFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
controller.send!(@filter, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class ProcFilter < Filter #:nodoc:
|
||||
def call(controller)
|
||||
@filter.call(controller)
|
||||
rescue LocalJumpError # a yield from a proc... no no bad dog.
|
||||
raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
|
||||
end
|
||||
end
|
||||
|
||||
class ProcWithCallFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.call(controller, block)
|
||||
rescue LocalJumpError # a yield from a proc... no no bad dog.
|
||||
raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
|
||||
end
|
||||
end
|
||||
|
||||
class MethodFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.call(controller, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class ClassFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.filter(controller, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class ClassBeforeFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.before(controller)
|
||||
end
|
||||
end
|
||||
|
||||
class ClassAfterFilter < Filter #:nodoc:
|
||||
def call(controller, &block)
|
||||
@filter.after(controller)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def append_filter_to_chain(filters, filter_type = :around, &block)
|
||||
pos = find_filter_append_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def prepend_filter_to_chain(filters, filter_type = :around, &block)
|
||||
pos = find_filter_prepend_position(filters, filter_type)
|
||||
update_filter_chain(filters, filter_type, pos, &block)
|
||||
end
|
||||
|
||||
def update_filter_chain(filters, filter_type, pos, &block)
|
||||
new_filters = create_filters(filters, filter_type, &block)
|
||||
new_chain = filter_chain.insert(pos, new_filters).flatten
|
||||
write_inheritable_attribute('filter_chain', new_chain)
|
||||
end
|
||||
|
||||
def find_filter_append_position(filters, filter_type)
|
||||
# appending an after filter puts it at the end of the call chain
|
||||
# before and around filters go before the first after filter in the chain
|
||||
unless filter_type == :after
|
||||
filter_chain.each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
|
||||
def find_filter_prepend_position(filters, filter_type)
|
||||
# prepending a before or around filter puts it at the front of the call chain
|
||||
# after filters go before the first after filter in the chain
|
||||
if filter_type == :after
|
||||
filter_chain.each_with_index do |f,i|
|
||||
return i if f.after?
|
||||
end
|
||||
return -1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
def create_filters(filters, filter_type, &block) #:nodoc:
|
||||
filters, conditions = extract_conditions(filters, &block)
|
||||
filters.map! { |filter| find_or_create_filter(filter, filter_type) }
|
||||
update_conditions(filters, conditions)
|
||||
filters
|
||||
end
|
||||
|
||||
def find_or_create_filter(filter, filter_type)
|
||||
if found_filter = find_filter(filter) { |f| f.type == filter_type }
|
||||
found_filter
|
||||
else
|
||||
f = class_for_filter(filter, filter_type).new(filter)
|
||||
# apply proxy to filter if necessary
|
||||
case filter_type
|
||||
when :before
|
||||
BeforeFilterProxy.new(f)
|
||||
when :after
|
||||
AfterFilterProxy.new(f)
|
||||
else
|
||||
f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The determination of the filter type was once done at run time.
|
||||
# This method is here to extract as much logic from the filter run time as possible
|
||||
def class_for_filter(filter, filter_type) #:nodoc:
|
||||
case
|
||||
when filter.is_a?(Symbol)
|
||||
SymbolFilter
|
||||
when filter.respond_to?(:call)
|
||||
if filter.is_a?(Method)
|
||||
MethodFilter
|
||||
elsif filter.arity == 1
|
||||
ProcFilter
|
||||
else
|
||||
ProcWithCallFilter
|
||||
end
|
||||
when filter.respond_to?(:filter)
|
||||
ClassFilter
|
||||
when filter.respond_to?(:before) && filter_type == :before
|
||||
ClassBeforeFilter
|
||||
when filter.respond_to?(:after) && filter_type == :after
|
||||
ClassAfterFilter
|
||||
else
|
||||
raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.')
|
||||
end
|
||||
end
|
||||
|
||||
def extract_conditions(*filters, &block) #:nodoc:
|
||||
filters.flatten!
|
||||
conditions = filters.extract_options!
|
||||
filters << block if block_given?
|
||||
return filters, conditions
|
||||
end
|
||||
|
||||
def update_conditions(filters, conditions)
|
||||
return if conditions.empty?
|
||||
if conditions[:only]
|
||||
write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only]))
|
||||
elsif conditions[:except]
|
||||
write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except]))
|
||||
end
|
||||
end
|
||||
|
||||
def condition_hash(filters, *actions)
|
||||
actions = actions.flatten.map(&:to_s)
|
||||
filters.inject({}) { |h,f| h.update( f => (actions.blank? ? nil : actions)) }
|
||||
end
|
||||
|
||||
def skip_filter_in_chain(*filters, &test) #:nodoc:
|
||||
filters, conditions = extract_conditions(filters)
|
||||
filters.map! { |f| block_given? ? find_filter(f, &test) : find_filter(f) }
|
||||
filters.compact!
|
||||
|
||||
if conditions.empty?
|
||||
delete_filters_in_chain(filters)
|
||||
else
|
||||
remove_actions_from_included_actions!(filters,conditions[:only] || [])
|
||||
conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
|
||||
update_conditions(filters,conditions)
|
||||
end
|
||||
end
|
||||
|
||||
def remove_actions_from_included_actions!(filters,*actions)
|
||||
actions = actions.flatten.map(&:to_s)
|
||||
updated_hash = filters.inject(read_inheritable_attribute('included_actions')||{}) do |hash,filter|
|
||||
ia = (hash[filter] || []) - actions
|
||||
ia.empty? ? hash.delete(filter) : hash[filter] = ia
|
||||
hash
|
||||
end
|
||||
write_inheritable_attribute('included_actions', updated_hash)
|
||||
end
|
||||
|
||||
def delete_filters_in_chain(filters) #:nodoc:
|
||||
write_inheritable_attribute('filter_chain', filter_chain.reject { |f| filters.include?(f) })
|
||||
end
|
||||
|
||||
def filter_responds_to_before_and_after(filter) #:nodoc:
|
||||
filter.respond_to?(:before) && filter.respond_to?(:after)
|
||||
end
|
||||
|
||||
def proxy_before_and_after_filter(filter) #:nodoc:
|
||||
return filter unless filter_responds_to_before_and_after(filter)
|
||||
Proc.new do |controller, action|
|
||||
filter.before(controller)
|
||||
|
||||
if controller.send!(:performed?)
|
||||
controller.send!(:halt_filter_chain, filter, :rendered_or_redirected)
|
||||
else
|
||||
begin
|
||||
action.call
|
||||
ensure
|
||||
filter.after(controller)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods # :nodoc:
|
||||
|
@ -679,89 +564,80 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
protected
|
||||
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
@before_filter_chain_aborted = false
|
||||
process_without_filters(request, response, method, *arguments)
|
||||
end
|
||||
|
||||
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||
@before_filter_chain_aborted = false
|
||||
process_without_filters(request, response, method, *arguments)
|
||||
end
|
||||
|
||||
def perform_action_with_filters
|
||||
call_filters(self.class.filter_chain, 0, 0)
|
||||
end
|
||||
def perform_action_with_filters
|
||||
call_filters(self.class.filter_chain, 0, 0)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def call_filters(chain, index, nesting)
|
||||
index = run_before_filters(chain, index, nesting)
|
||||
aborted = @before_filter_chain_aborted
|
||||
perform_action_without_filters unless performed? || aborted
|
||||
return index if nesting != 0 || aborted
|
||||
run_after_filters(chain, index)
|
||||
end
|
||||
|
||||
def skip_excluded_filters(chain, index)
|
||||
while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name)
|
||||
index = index.next
|
||||
def call_filters(chain, index, nesting)
|
||||
index = run_before_filters(chain, index, nesting)
|
||||
aborted = @before_filter_chain_aborted
|
||||
perform_action_without_filters unless performed? || aborted
|
||||
return index if nesting != 0 || aborted
|
||||
run_after_filters(chain, index)
|
||||
end
|
||||
[filter, index]
|
||||
end
|
||||
|
||||
def run_before_filters(chain, index, nesting)
|
||||
while chain[index]
|
||||
filter, index = skip_excluded_filters(chain, index)
|
||||
break unless filter # end of call chain reached
|
||||
def run_before_filters(chain, index, nesting)
|
||||
while chain[index]
|
||||
filter, index = chain[index], index
|
||||
break unless filter # end of call chain reached
|
||||
|
||||
case filter.type
|
||||
when :before
|
||||
filter.run(self) # invoke before filter
|
||||
index = index.next
|
||||
break if @before_filter_chain_aborted
|
||||
when :around
|
||||
yielded = false
|
||||
case filter
|
||||
when BeforeFilter
|
||||
filter.call(self) # invoke before filter
|
||||
index = index.next
|
||||
break if @before_filter_chain_aborted
|
||||
when AroundFilter
|
||||
yielded = false
|
||||
|
||||
filter.call(self) do
|
||||
yielded = true
|
||||
# all remaining before and around filters will be run in this call
|
||||
index = call_filters(chain, index.next, nesting.next)
|
||||
filter.call(self) do
|
||||
yielded = true
|
||||
# all remaining before and around filters will be run in this call
|
||||
index = call_filters(chain, index.next, nesting.next)
|
||||
end
|
||||
|
||||
halt_filter_chain(filter, :did_not_yield) unless yielded
|
||||
|
||||
break
|
||||
else
|
||||
break # no before or around filters left
|
||||
end
|
||||
end
|
||||
|
||||
index
|
||||
end
|
||||
|
||||
def run_after_filters(chain, index)
|
||||
seen_after_filter = false
|
||||
|
||||
while chain[index]
|
||||
filter, index = chain[index], index
|
||||
break unless filter # end of call chain reached
|
||||
|
||||
case filter
|
||||
when AfterFilter
|
||||
seen_after_filter = true
|
||||
filter.call(self) # invoke after filter
|
||||
else
|
||||
# implementation error or someone has mucked with the filter chain
|
||||
raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
|
||||
end
|
||||
|
||||
halt_filter_chain(filter, :did_not_yield) unless yielded
|
||||
|
||||
break
|
||||
else
|
||||
break # no before or around filters left
|
||||
end
|
||||
end
|
||||
|
||||
index
|
||||
end
|
||||
|
||||
def run_after_filters(chain, index)
|
||||
seen_after_filter = false
|
||||
|
||||
while chain[index]
|
||||
filter, index = skip_excluded_filters(chain, index)
|
||||
break unless filter # end of call chain reached
|
||||
|
||||
case filter.type
|
||||
when :after
|
||||
seen_after_filter = true
|
||||
filter.run(self) # invoke after filter
|
||||
else
|
||||
# implementation error or someone has mucked with the filter chain
|
||||
raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
|
||||
index = index.next
|
||||
end
|
||||
|
||||
index = index.next
|
||||
index.next
|
||||
end
|
||||
|
||||
index.next
|
||||
end
|
||||
|
||||
def halt_filter_chain(filter, reason)
|
||||
@before_filter_chain_aborted = true
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
|
||||
end
|
||||
def halt_filter_chain(filter, reason)
|
||||
@before_filter_chain_aborted = true
|
||||
logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,7 +28,6 @@ module ActionController #:nodoc:
|
|||
base.class_eval do
|
||||
include InstanceMethods
|
||||
alias_method_chain :assign_shortcuts, :flash
|
||||
alias_method_chain :process_cleanup, :flash
|
||||
alias_method_chain :reset_session, :flash
|
||||
end
|
||||
end
|
||||
|
@ -166,11 +165,7 @@ module ActionController #:nodoc:
|
|||
def assign_shortcuts_with_flash(request, response) #:nodoc:
|
||||
assign_shortcuts_without_flash(request, response)
|
||||
flash(:refresh)
|
||||
end
|
||||
|
||||
def process_cleanup_with_flash
|
||||
flash.sweep if @_session
|
||||
process_cleanup_without_flash
|
||||
flash.sweep if @_session && !component_request?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
31
vendor/rails/actionpack/lib/action_controller/headers.rb
vendored
Normal file
31
vendor/rails/actionpack/lib/action_controller/headers.rb
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
module ActionController
|
||||
module Http
|
||||
class Headers < ::Hash
|
||||
|
||||
def initialize(constructor = {})
|
||||
if constructor.is_a?(Hash)
|
||||
super()
|
||||
update(constructor)
|
||||
else
|
||||
super(constructor)
|
||||
end
|
||||
end
|
||||
|
||||
def [](header_name)
|
||||
if include?(header_name)
|
||||
super
|
||||
else
|
||||
super(normalize_header(header_name))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
# Takes an HTTP header name and returns it in the
|
||||
# format
|
||||
def normalize_header(header_name)
|
||||
"HTTP_#{header_name.upcase.gsub(/-/, '_')}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -143,11 +143,19 @@ module ActionController #:nodoc:
|
|||
# Declare a controller method as a helper. For example, the following
|
||||
# makes the +current_user+ controller method available to the view:
|
||||
# class ApplicationController < ActionController::Base
|
||||
# helper_method :current_user
|
||||
# helper_method :current_user, :logged_in?
|
||||
#
|
||||
# def current_user
|
||||
# @current_user ||= User.find(session[:user])
|
||||
# @current_user ||= User.find_by_id(session[:user])
|
||||
# end
|
||||
#
|
||||
# def logged_in?
|
||||
# current_user != nil
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# In a view:
|
||||
# <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
|
||||
def helper_method(*methods)
|
||||
methods.flatten.each do |method|
|
||||
master_helper_module.module_eval <<-end_eval
|
||||
|
@ -167,6 +175,15 @@ module ActionController #:nodoc:
|
|||
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
|
||||
end
|
||||
|
||||
# Provides a proxy to access helpers methods from outside the view.
|
||||
def helpers
|
||||
unless @helper_proxy
|
||||
@helper_proxy = ActionView::Base.new
|
||||
@helper_proxy.extend master_helper_module
|
||||
else
|
||||
@helper_proxy
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def default_helper_module!
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
require 'base64'
|
||||
|
||||
module ActionController
|
||||
module HttpAuthentication
|
||||
# Makes it dead easy to do HTTP Basic authentication.
|
||||
|
@ -72,7 +70,7 @@ module ActionController
|
|||
#
|
||||
# On shared hosts, Apache sometimes doesn't pass authentication headers to
|
||||
# FCGI instances. If your environment matches this description and you cannot
|
||||
# authenticate, try this rule in public/.htaccess (replace the plain one):
|
||||
# authenticate, try this rule in your Apache setup:
|
||||
#
|
||||
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
|
||||
module Basic
|
||||
|
@ -110,11 +108,11 @@ module ActionController
|
|||
end
|
||||
|
||||
def decode_credentials(request)
|
||||
Base64.decode64(authorization(request).split.last || '')
|
||||
ActiveSupport::Base64.decode64(authorization(request).split.last || '')
|
||||
end
|
||||
|
||||
def encode_credentials(user_name, password)
|
||||
"Basic #{Base64.encode64("#{user_name}:#{password}")}"
|
||||
"Basic #{ActiveSupport::Base64.encode64("#{user_name}:#{password}")}"
|
||||
end
|
||||
|
||||
def authentication_request(controller, realm)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require 'dispatcher'
|
||||
require 'stringio'
|
||||
require 'uri'
|
||||
|
||||
require 'action_controller/dispatcher'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
module ActionController
|
||||
|
@ -54,6 +55,9 @@ module ActionController
|
|||
# A running counter of the number of requests processed.
|
||||
attr_accessor :request_count
|
||||
|
||||
class MultiPartNeededException < Exception
|
||||
end
|
||||
|
||||
# Create and initialize a new +Session+ instance.
|
||||
def initialize
|
||||
reset!
|
||||
|
@ -276,7 +280,7 @@ module ActionController
|
|||
ActionController::Base.clear_last_instantiation!
|
||||
|
||||
cgi = StubCGI.new(env, data)
|
||||
Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
|
||||
ActionController::Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
|
||||
@result = cgi.stdoutput.string
|
||||
@request_count += 1
|
||||
|
||||
|
@ -293,15 +297,19 @@ module ActionController
|
|||
|
||||
parse_result
|
||||
return status
|
||||
rescue MultiPartNeededException
|
||||
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
|
||||
status = process(method, path, multipart_body(parameters, boundary), (headers || {}).merge({"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
|
||||
return status
|
||||
end
|
||||
|
||||
# Parses the result of the response and extracts the various values,
|
||||
# like cookies, status, headers, etc.
|
||||
def parse_result
|
||||
headers, result_body = @result.split(/\r\n\r\n/, 2)
|
||||
response_headers, result_body = @result.split(/\r\n\r\n/, 2)
|
||||
|
||||
@headers = Hash.new { |h,k| h[k] = [] }
|
||||
headers.each_line do |line|
|
||||
response_headers.to_s.each_line do |line|
|
||||
key, value = line.strip.split(/:\s*/, 2)
|
||||
@headers[key.downcase] << value
|
||||
end
|
||||
|
@ -311,7 +319,7 @@ module ActionController
|
|||
@cookies[name] = value
|
||||
end
|
||||
|
||||
@status, @status_message = @headers["status"].first.split(/ /)
|
||||
@status, @status_message = @headers["status"].first.to_s.split(/ /)
|
||||
@status = @status.to_i
|
||||
end
|
||||
|
||||
|
@ -341,7 +349,9 @@ module ActionController
|
|||
# Convert the given parameters to a request string. The parameters may
|
||||
# be a string, +nil+, or a Hash.
|
||||
def requestify(parameters, prefix=nil)
|
||||
if Hash === parameters
|
||||
if TestUploadedFile === parameters
|
||||
raise MultiPartNeededException
|
||||
elsif Hash === parameters
|
||||
return nil if parameters.empty?
|
||||
parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
|
||||
elsif Array === parameters
|
||||
|
@ -352,6 +362,45 @@ module ActionController
|
|||
"#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
|
||||
end
|
||||
end
|
||||
|
||||
def multipart_requestify(params, first=true)
|
||||
returning Hash.new do |p|
|
||||
params.each do |key, value|
|
||||
k = first ? CGI.escape(key.to_s) : "[#{CGI.escape(key.to_s)}]"
|
||||
if Hash === value
|
||||
multipart_requestify(value, false).each do |subkey, subvalue|
|
||||
p[k + subkey] = subvalue
|
||||
end
|
||||
else
|
||||
p[k] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def multipart_body(params, boundary)
|
||||
multipart_requestify(params).map do |key, value|
|
||||
if value.respond_to?(:original_filename)
|
||||
File.open(value.path) do |f|
|
||||
<<-EOF
|
||||
--#{boundary}\r
|
||||
Content-Disposition: form-data; name="#{key}"; filename="#{CGI.escape(value.original_filename)}"\r
|
||||
Content-Type: #{value.content_type}\r
|
||||
Content-Length: #{File.stat(value.path).size}\r
|
||||
\r
|
||||
#{f.read}\r
|
||||
EOF
|
||||
end
|
||||
else
|
||||
<<-EOF
|
||||
--#{boundary}\r
|
||||
Content-Disposition: form-data; name="#{key}"\r
|
||||
\r
|
||||
#{value}\r
|
||||
EOF
|
||||
end
|
||||
end.join("")+"--#{boundary}--\r"
|
||||
end
|
||||
end
|
||||
|
||||
# A module used to extend ActionController::Base, so that integration tests
|
||||
|
|
|
@ -29,18 +29,20 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# // The header part of this layout
|
||||
# <%= yield %>
|
||||
# // The footer part of this layout -->
|
||||
# // The footer part of this layout
|
||||
#
|
||||
# And then you have content pages that look like this:
|
||||
#
|
||||
# hello world
|
||||
#
|
||||
# Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
|
||||
# like this:
|
||||
# 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 -->
|
||||
# // The footer part of this layout
|
||||
#
|
||||
# NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
|
||||
# variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
|
||||
#
|
||||
# == Accessing shared variables
|
||||
#
|
||||
|
@ -124,7 +126,7 @@ module ActionController #:nodoc:
|
|||
# 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 +app/views/layouts/+.
|
||||
# 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
|
||||
|
@ -149,21 +151,18 @@ module ActionController #:nodoc:
|
|||
# == 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.
|
||||
# Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
|
||||
# This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully
|
||||
# qualified template and layout names as this example shows:
|
||||
# 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/index", :layout => "help"
|
||||
# render :action => "help", :layout => "help"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout
|
||||
# as the third.
|
||||
#
|
||||
# NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
|
||||
# variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
|
||||
# This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout.
|
||||
module ClassMethods
|
||||
# If a layout is specified, all rendered actions will have their result rendered
|
||||
# when the layout <tt>yield</tt>s. This layout can itself depend on instance variables assigned during action
|
||||
|
@ -187,9 +186,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def layout_list #:nodoc:
|
||||
view_paths.collect do |path|
|
||||
Dir["#{path}/layouts/**/*"]
|
||||
end.flatten
|
||||
Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] }
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -209,12 +206,6 @@ module ActionController #:nodoc:
|
|||
conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
|
||||
end
|
||||
|
||||
def layout_directory_exists_cache
|
||||
@@layout_directory_exists_cache ||= Hash.new do |h, dirname|
|
||||
h[dirname] = File.directory? dirname
|
||||
end
|
||||
end
|
||||
|
||||
def default_layout_with_format(format, layout)
|
||||
list = layout_list
|
||||
if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty?
|
||||
|
@ -250,16 +241,14 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
protected
|
||||
def render_with_a_layout(options = nil, &block) #:nodoc:
|
||||
def render_with_a_layout(options = nil, extra_options = {}, &block) #:nodoc:
|
||||
template_with_options = options.is_a?(Hash)
|
||||
|
||||
if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options))
|
||||
assert_existence_of_template_file(layout)
|
||||
|
||||
if (layout = pick_layout(template_with_options, options)) && apply_layout?(template_with_options, options)
|
||||
options = options.merge :layout => false if template_with_options
|
||||
logger.info("Rendering template within #{layout}") if logger
|
||||
|
||||
content_for_layout = render_with_no_layout(options, &block)
|
||||
content_for_layout = render_with_no_layout(options, extra_options, &block)
|
||||
erase_render_results
|
||||
add_variables_to_assigns
|
||||
@template.instance_variable_set("@content_for_layout", content_for_layout)
|
||||
|
@ -267,7 +256,7 @@ module ActionController #:nodoc:
|
|||
status = template_with_options ? options[:status] : nil
|
||||
render_for_text(@template.render_file(layout, true), status)
|
||||
else
|
||||
render_with_no_layout(options, &block)
|
||||
render_with_no_layout(options, extra_options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -314,13 +303,8 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Does a layout directory for this class exist?
|
||||
# we cache this info in a class level hash
|
||||
def layout_directory?(layout_name)
|
||||
view_paths.find do |path|
|
||||
next unless template_path = Dir[File.join(path, 'layouts', layout_name) + ".*"].first
|
||||
self.class.send!(:layout_directory_exists_cache)[File.dirname(template_path)]
|
||||
end
|
||||
@template.finder.find_template_extension_from_handler(File.join('layouts', layout_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -125,7 +125,7 @@ module ActionController #:nodoc:
|
|||
|
||||
@order << mime_type
|
||||
|
||||
@responses[mime_type] = Proc.new do
|
||||
@responses[mime_type] ||= Proc.new do
|
||||
@response.template.template_format = mime_type.to_sym
|
||||
@response.content_type = mime_type.to_s
|
||||
block_given? ? block.call : @controller.send(:render, :action => @controller.action_name)
|
||||
|
@ -133,7 +133,11 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def any(*args, &block)
|
||||
args.each { |type| send(type, &block) }
|
||||
if args.any?
|
||||
args.each { |type| send(type, &block) }
|
||||
else
|
||||
custom(@mime_type_priority.first, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing(symbol, &block)
|
||||
|
|
|
@ -71,8 +71,11 @@ module Mime
|
|||
# keep track of creation order to keep the subsequent sort stable
|
||||
list = []
|
||||
accept_header.split(/,/).each_with_index do |header, index|
|
||||
params = header.split(/;\s*q=/)
|
||||
list << AcceptItem.new(index, *params) unless params.empty?
|
||||
params, q = header.split(/;\s*q=/)
|
||||
if params
|
||||
params.strip!
|
||||
list << AcceptItem.new(index, params, q) unless params.empty?
|
||||
end
|
||||
end
|
||||
list.sort!
|
||||
|
||||
|
@ -145,7 +148,10 @@ module Mime
|
|||
end
|
||||
|
||||
def ==(mime_type)
|
||||
(@synonyms + [ self ]).any? { |synonym| synonym.to_s == mime_type.to_s } if mime_type
|
||||
return false if mime_type.blank?
|
||||
(@synonyms + [ self ]).any? do |synonym|
|
||||
synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,8 +1,81 @@
|
|||
module ActionController
|
||||
# Polymorphic URL helpers are methods for smart resolution to a named route call when
|
||||
# given an ActiveRecord model instance. They are to be used in combination with
|
||||
# ActionController::Resources.
|
||||
#
|
||||
# These methods are useful when you want to generate correct URL or path to a RESTful
|
||||
# resource without having to know the exact type of the record in question.
|
||||
#
|
||||
# Nested resources and/or namespaces are also supported, as illustrated in the example:
|
||||
#
|
||||
# polymorphic_url([:admin, @article, @comment])
|
||||
# #-> results in:
|
||||
# admin_article_comment_url(@article, @comment)
|
||||
#
|
||||
# == Usage within the framework
|
||||
#
|
||||
# Polymorphic URL helpers are used in a number of places throughout the Rails framework:
|
||||
#
|
||||
# * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
|
||||
# <tt>url_for(@article)</tt>;
|
||||
# * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
|
||||
# <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
|
||||
# action;
|
||||
# * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
|
||||
# <tt>redirect_to(post)</tt> in your controllers;
|
||||
# * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
|
||||
# for feed entries.
|
||||
#
|
||||
# == Prefixed polymorphic helpers
|
||||
#
|
||||
# In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
|
||||
# number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
|
||||
# in options. Those are:
|
||||
#
|
||||
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
|
||||
# * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
|
||||
# * <tt>formatted_polymorphic_url</tt>, <tt>formatted_polymorphic_path</tt>
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# edit_polymorphic_path(@post)
|
||||
# #=> /posts/1/edit
|
||||
#
|
||||
# formatted_polymorphic_path([@post, :pdf])
|
||||
# #=> /posts/1.pdf
|
||||
module PolymorphicRoutes
|
||||
# Constructs a call to a named RESTful route for the given record and returns the
|
||||
# resulting URL string. For example:
|
||||
#
|
||||
# # calls post_url(post)
|
||||
# polymorphic_url(post) # => "http://example.com/posts/1"
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:action</tt> - Specifies the action prefix for the named route:
|
||||
# <tt>:new</tt>, <tt>:edit</tt>, or <tt>:formatted</tt>. Default is no prefix.
|
||||
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
|
||||
# Default is <tt>:url</tt>.
|
||||
#
|
||||
# ==== Examples
|
||||
#
|
||||
# # an Article record
|
||||
# polymorphic_url(record) # same as article_url(record)
|
||||
#
|
||||
# # a Comment record
|
||||
# polymorphic_url(record) # same as comment_url(record)
|
||||
#
|
||||
# # it recognizes new records and maps to the collection
|
||||
# record = Comment.new
|
||||
# polymorphic_url(record) # same as comments_url()
|
||||
#
|
||||
def polymorphic_url(record_or_hash_or_array, options = {})
|
||||
record = extract_record(record_or_hash_or_array)
|
||||
if record_or_hash_or_array.kind_of?(Array)
|
||||
record_or_hash_or_array = record_or_hash_or_array.dup
|
||||
end
|
||||
|
||||
record = extract_record(record_or_hash_or_array)
|
||||
format = extract_format(record_or_hash_or_array, options)
|
||||
namespace = extract_namespace(record_or_hash_or_array)
|
||||
|
||||
args = case record_or_hash_or_array
|
||||
|
@ -11,9 +84,11 @@ module ActionController
|
|||
else [ record_or_hash_or_array ]
|
||||
end
|
||||
|
||||
args << format if format
|
||||
|
||||
inflection =
|
||||
case
|
||||
when options[:action] == "new"
|
||||
when options[:action].to_s == "new"
|
||||
args.pop
|
||||
:singular
|
||||
when record.respond_to?(:new_record?) && record.new_record?
|
||||
|
@ -27,8 +102,11 @@ module ActionController
|
|||
send!(named_route, *args)
|
||||
end
|
||||
|
||||
def polymorphic_path(record_or_hash_or_array)
|
||||
polymorphic_url(record_or_hash_or_array, :routing_type => :path)
|
||||
# Returns the path component of a URL for the given record. It uses
|
||||
# <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
|
||||
def polymorphic_path(record_or_hash_or_array, options = {})
|
||||
options[:routing_type] = :path
|
||||
polymorphic_url(record_or_hash_or_array, options)
|
||||
end
|
||||
|
||||
%w(edit new formatted).each do |action|
|
||||
|
@ -43,26 +121,29 @@ module ActionController
|
|||
EOT
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def action_prefix(options)
|
||||
options[:action] ? "#{options[:action]}_" : ""
|
||||
end
|
||||
|
||||
def routing_type(options)
|
||||
"#{options[:routing_type] || "url"}"
|
||||
options[:routing_type] || :url
|
||||
end
|
||||
|
||||
def build_named_route_call(records, namespace, inflection, options = {})
|
||||
records = Array.new([extract_record(records)]) unless records.is_a?(Array)
|
||||
base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", records.pop)}_"
|
||||
|
||||
method_root = records.reverse.inject(base_segment) do |string, name|
|
||||
segment = "#{RecordIdentifier.send!("singular_class_name", name)}_"
|
||||
segment << string
|
||||
unless records.is_a?(Array)
|
||||
record = extract_record(records)
|
||||
route = ''
|
||||
else
|
||||
record = records.pop
|
||||
route = records.inject("") do |string, parent|
|
||||
string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
|
||||
end
|
||||
end
|
||||
|
||||
action_prefix(options) + namespace + method_root + routing_type(options)
|
||||
route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
|
||||
|
||||
action_prefix(options) + namespace + route + routing_type(options).to_s
|
||||
end
|
||||
|
||||
def extract_record(record_or_hash_or_array)
|
||||
|
@ -73,12 +154,22 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
def extract_format(record_or_hash_or_array, options)
|
||||
if options[:action].to_s == "formatted" && record_or_hash_or_array.is_a?(Array)
|
||||
record_or_hash_or_array.pop
|
||||
elsif options[:format]
|
||||
options[:format]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def extract_namespace(record_or_hash_or_array)
|
||||
returning "" do |namespace|
|
||||
if record_or_hash_or_array.is_a?(Array)
|
||||
record_or_hash_or_array.delete_if do |record_or_namespace|
|
||||
if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol)
|
||||
namespace << "#{record_or_namespace.to_s}_"
|
||||
namespace << "#{record_or_namespace}_"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue