mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-18 16:20:12 +01:00
Vendoring Rails 2.3.5
This commit is contained in:
parent
3e83d19299
commit
f8779795ce
943 changed files with 56503 additions and 61351 deletions
37
vendor/rails/actionmailer/CHANGELOG
vendored
37
vendor/rails/actionmailer/CHANGELOG
vendored
|
|
@ -1,3 +1,24 @@
|
||||||
|
*2.3.5 (November 25, 2009)*
|
||||||
|
|
||||||
|
* Minor Bug Fixes and deprecation warnings
|
||||||
|
|
||||||
|
*2.3.4 (September 4, 2009)*
|
||||||
|
|
||||||
|
* Minor bug fixes.
|
||||||
|
|
||||||
|
*2.3.3 (July 12, 2009)*
|
||||||
|
|
||||||
|
* No changes, just a version bump.
|
||||||
|
|
||||||
|
*2.3.2 [Final] (March 15, 2009)*
|
||||||
|
|
||||||
|
* Fixed that ActionMailer should send correctly formatted Return-Path in MAIL FROM for SMTP #1842 [Matt Jones]
|
||||||
|
|
||||||
|
* Fixed RFC-2045 quoted-printable bug #1421 [squadette]
|
||||||
|
|
||||||
|
* Fixed that no body charset would be set when there are attachments present #740 [Paweł Kondzior]
|
||||||
|
|
||||||
|
|
||||||
*2.2.1 [RC2] (November 14th, 2008)*
|
*2.2.1 [RC2] (November 14th, 2008)*
|
||||||
|
|
||||||
* Turn on STARTTLS if it is available in Net::SMTP (added in Ruby 1.8.7) and the SMTP server supports it (This is required for Gmail's SMTP server) #1336 [Grant Hollingworth]
|
* Turn on STARTTLS if it is available in Net::SMTP (added in Ruby 1.8.7) and the SMTP server supports it (This is required for Gmail's SMTP server) #1336 [Grant Hollingworth]
|
||||||
|
|
@ -5,7 +26,7 @@
|
||||||
|
|
||||||
*2.2.0 [RC1] (October 24th, 2008)*
|
*2.2.0 [RC1] (October 24th, 2008)*
|
||||||
|
|
||||||
* Add layout functionality to mailers [Pratik]
|
* Add layout functionality to mailers [Pratik Naik]
|
||||||
|
|
||||||
Mailer layouts behaves just like controller layouts, except layout names need to
|
Mailer layouts behaves just like controller layouts, except layout names need to
|
||||||
have '_mailer' postfix for them to be automatically picked up.
|
have '_mailer' postfix for them to be automatically picked up.
|
||||||
|
|
@ -17,7 +38,7 @@
|
||||||
|
|
||||||
* Less verbose mail logging: just recipients for :info log level; the whole email for :debug only. #8000 [iaddict, Tarmo Tänav]
|
* 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]
|
* Updated TMail to version 1.2.1 [Mikel Lindsaar]
|
||||||
|
|
||||||
* Fixed that you don't have to call super in ActionMailer::TestCase#setup #10406 [jamesgolick]
|
* Fixed that you don't have to call super in ActionMailer::TestCase#setup #10406 [jamesgolick]
|
||||||
|
|
||||||
|
|
@ -29,7 +50,7 @@
|
||||||
|
|
||||||
*2.0.1* (December 7th, 2007)
|
*2.0.1* (December 7th, 2007)
|
||||||
|
|
||||||
* Update ActionMailer so it treats ActionView the same way that ActionController does. Closes #10244 [rick]
|
* Update ActionMailer so it treats ActionView the same way that ActionController does. Closes #10244 [Rick Olson]
|
||||||
|
|
||||||
* Pass the template_root as an array as ActionView's view_path
|
* Pass the template_root as an array as ActionView's view_path
|
||||||
* Request templates with the "#{mailer_name}/#{action}" as opposed to just "#{action}"
|
* Request templates with the "#{mailer_name}/#{action}" as opposed to just "#{action}"
|
||||||
|
|
@ -38,11 +59,11 @@
|
||||||
|
|
||||||
* Update README to use new smtp settings configuration API. Closes #10060 [psq]
|
* Update README to use new smtp settings configuration API. Closes #10060 [psq]
|
||||||
|
|
||||||
* Allow ActionMailer subclasses to individually set their delivery method (so two subclasses can have different delivery methods) #10033 [zdennis]
|
* Allow ActionMailer subclasses to individually set their delivery method (so two subclasses can have different delivery methods) #10033 [Zach Dennis]
|
||||||
|
|
||||||
* Update TMail to v1.1.0. Use an updated version of TMail if available. [mikel]
|
* Update TMail to v1.1.0. Use an updated version of TMail if available. [Mikel Lindsaar]
|
||||||
|
|
||||||
* Introduce a new base test class for testing Mailers. ActionMailer::TestCase [Koz]
|
* Introduce a new base test class for testing Mailers. ActionMailer::TestCase [Michael Koziarski]
|
||||||
|
|
||||||
* Fix silent failure of rxml templates. #9879 [jstewart]
|
* Fix silent failure of rxml templates. #9879 [jstewart]
|
||||||
|
|
||||||
|
|
@ -77,7 +98,7 @@
|
||||||
|
|
||||||
*1.3.2* (February 5th, 2007)
|
*1.3.2* (February 5th, 2007)
|
||||||
|
|
||||||
* Deprecate server_settings renaming it to smtp_settings, add sendmail_settings to allow you to override the arguments to and location of the sendmail executable. [Koz]
|
* Deprecate server_settings renaming it to smtp_settings, add sendmail_settings to allow you to override the arguments to and location of the sendmail executable. [Michael Koziarski]
|
||||||
|
|
||||||
|
|
||||||
*1.3.1* (January 16th, 2007)
|
*1.3.1* (January 16th, 2007)
|
||||||
|
|
@ -97,7 +118,7 @@
|
||||||
|
|
||||||
* Tighten rescue clauses. #5985 [james@grayproductions.net]
|
* Tighten rescue clauses. #5985 [james@grayproductions.net]
|
||||||
|
|
||||||
* Automatically included ActionController::UrlWriter, such that URL generation can happen within ActionMailer controllers. [DHH]
|
* Automatically included ActionController::UrlWriter, such that URL generation can happen within ActionMailer controllers. [David Heinemeier Hansson]
|
||||||
|
|
||||||
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
|
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
|
||||||
|
|
||||||
|
|
|
||||||
2
vendor/rails/actionmailer/MIT-LICENSE
vendored
2
vendor/rails/actionmailer/MIT-LICENSE
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2004-2008 David Heinemeier Hansson
|
Copyright (c) 2004-2009 David Heinemeier Hansson
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
a copy of this software and associated documentation files (the
|
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,6 @@ require 'rake/testtask'
|
||||||
require 'rake/rdoctask'
|
require 'rake/rdoctask'
|
||||||
require 'rake/packagetask'
|
require 'rake/packagetask'
|
||||||
require 'rake/gempackagetask'
|
require 'rake/gempackagetask'
|
||||||
require 'rake/contrib/sshpublisher'
|
|
||||||
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
|
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
|
||||||
|
|
||||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||||
|
|
@ -55,7 +54,7 @@ spec = Gem::Specification.new do |s|
|
||||||
s.rubyforge_project = "actionmailer"
|
s.rubyforge_project = "actionmailer"
|
||||||
s.homepage = "http://www.rubyonrails.org"
|
s.homepage = "http://www.rubyonrails.org"
|
||||||
|
|
||||||
s.add_dependency('actionpack', '= 2.2.2' + PKG_BUILD)
|
s.add_dependency('actionpack', '= 2.3.5' + PKG_BUILD)
|
||||||
|
|
||||||
s.has_rdoc = true
|
s.has_rdoc = true
|
||||||
s.requirements << 'none'
|
s.requirements << 'none'
|
||||||
|
|
@ -76,12 +75,14 @@ end
|
||||||
|
|
||||||
desc "Publish the API documentation"
|
desc "Publish the API documentation"
|
||||||
task :pgem => [:package] do
|
task :pgem => [:package] do
|
||||||
|
require 'rake/contrib/sshpublisher'
|
||||||
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||||
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
|
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Publish the API documentation"
|
desc "Publish the API documentation"
|
||||||
task :pdoc => [:rdoc] do
|
task :pdoc => [:rdoc] do
|
||||||
|
require 'rake/contrib/sshpublisher'
|
||||||
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload
|
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
42
vendor/rails/actionmailer/lib/action_mailer.rb
vendored
42
vendor/rails/actionmailer/lib/action_mailer.rb
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
#--
|
#--
|
||||||
# Copyright (c) 2004-2008 David Heinemeier Hansson
|
# Copyright (c) 2004-2009 David Heinemeier Hansson
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
# Permission is hereby granted, free of charge, to any person obtaining
|
||||||
# a copy of this software and associated documentation files (the
|
# a copy of this software and associated documentation files (the
|
||||||
|
|
@ -31,22 +31,32 @@ rescue LoadError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require 'action_mailer/vendor'
|
require 'action_view'
|
||||||
require 'tmail'
|
|
||||||
|
|
||||||
require 'action_mailer/base'
|
module ActionMailer
|
||||||
require 'action_mailer/helpers'
|
def self.load_all!
|
||||||
require 'action_mailer/mail_helper'
|
[Base, Part, ::Text::Format, ::Net::SMTP]
|
||||||
require 'action_mailer/quoting'
|
end
|
||||||
require 'action_mailer/test_helper'
|
|
||||||
|
|
||||||
require 'net/smtp'
|
autoload :AdvAttrAccessor, 'action_mailer/adv_attr_accessor'
|
||||||
|
autoload :Base, 'action_mailer/base'
|
||||||
ActionMailer::Base.class_eval do
|
autoload :Helpers, 'action_mailer/helpers'
|
||||||
include ActionMailer::Quoting
|
autoload :Part, 'action_mailer/part'
|
||||||
include ActionMailer::Helpers
|
autoload :PartContainer, 'action_mailer/part_container'
|
||||||
|
autoload :Quoting, 'action_mailer/quoting'
|
||||||
helper MailHelper
|
autoload :TestCase, 'action_mailer/test_case'
|
||||||
|
autoload :TestHelper, 'action_mailer/test_helper'
|
||||||
|
autoload :Utils, 'action_mailer/utils'
|
||||||
end
|
end
|
||||||
|
|
||||||
silence_warnings { TMail::Encoder.const_set("MAX_LINE_LEN", 200) }
|
module Text
|
||||||
|
autoload :Format, 'action_mailer/vendor/text_format'
|
||||||
|
end
|
||||||
|
|
||||||
|
module Net
|
||||||
|
autoload :SMTP, 'net/smtp'
|
||||||
|
end
|
||||||
|
|
||||||
|
autoload :MailHelper, 'action_mailer/mail_helper'
|
||||||
|
|
||||||
|
require 'action_mailer/vendor/tmail'
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,3 @@
|
||||||
require 'action_mailer/adv_attr_accessor'
|
|
||||||
require 'action_mailer/part'
|
|
||||||
require 'action_mailer/part_container'
|
|
||||||
require 'action_mailer/utils'
|
|
||||||
require 'tmail/net'
|
|
||||||
|
|
||||||
module ActionMailer #:nodoc:
|
module ActionMailer #:nodoc:
|
||||||
# Action Mailer allows you to send email from your application using a mailer model and views.
|
# Action Mailer allows you to send email from your application using a mailer model and views.
|
||||||
#
|
#
|
||||||
|
|
@ -23,6 +17,7 @@ module ActionMailer #:nodoc:
|
||||||
# class Notifier < ActionMailer::Base
|
# class Notifier < ActionMailer::Base
|
||||||
# def signup_notification(recipient)
|
# def signup_notification(recipient)
|
||||||
# recipients recipient.email_address_with_name
|
# recipients recipient.email_address_with_name
|
||||||
|
# bcc ["bcc@example.com", "Order Watcher <watcher@example.com>"]
|
||||||
# from "system@example.com"
|
# from "system@example.com"
|
||||||
# subject "New account information"
|
# subject "New account information"
|
||||||
# body :account => recipient
|
# body :account => recipient
|
||||||
|
|
@ -218,6 +213,8 @@ module ActionMailer #:nodoc:
|
||||||
# * <tt>:password</tt> - If your mail server requires authentication, set the password 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.
|
# * <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>.
|
# This is a symbol and one of <tt>:plain</tt>, <tt>:login</tt>, <tt>:cram_md5</tt>.
|
||||||
|
# * <tt>:enable_starttls_auto</tt> - When set to true, detects if STARTTLS is enabled in your SMTP server and starts to use it.
|
||||||
|
# It works only on Ruby >= 1.8.7 and Ruby >= 1.9. Default is true.
|
||||||
#
|
#
|
||||||
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
|
# * <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 <tt>/usr/sbin/sendmail</tt>.
|
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
|
||||||
|
|
@ -235,17 +232,20 @@ module ActionMailer #:nodoc:
|
||||||
#
|
#
|
||||||
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
|
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
|
||||||
# pick a different charset from inside a method with +charset+.
|
# pick a different charset from inside a method with +charset+.
|
||||||
|
#
|
||||||
# * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You
|
# * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You
|
||||||
# can also pick a different content type from inside a method with +content_type+.
|
# can also pick a different content type from inside a method with +content_type+.
|
||||||
|
#
|
||||||
# * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to <tt>1.0</tt>. You
|
# * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to <tt>1.0</tt>. You
|
||||||
# can also pick a different value from inside a method with +mime_version+.
|
# can also pick a different value from inside a method with +mime_version+.
|
||||||
|
#
|
||||||
# * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates
|
# * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates
|
||||||
# which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to
|
# which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to
|
||||||
# <tt>["text/html", "text/enriched", "text/plain"]</tt>. Items that appear first in the array have higher priority in the mail client
|
# <tt>["text/html", "text/enriched", "text/plain"]</tt>. Items that appear first in the array have higher priority in the mail client
|
||||||
# and appear last in the mime encoded message. You can also pick a different order from inside a method with
|
# and appear last in the mime encoded message. You can also pick a different order from inside a method with
|
||||||
# +implicit_parts_order+.
|
# +implicit_parts_order+.
|
||||||
class Base
|
class Base
|
||||||
include AdvAttrAccessor, PartContainer
|
include AdvAttrAccessor, PartContainer, Quoting, Utils
|
||||||
if Object.const_defined?(:ActionController)
|
if Object.const_defined?(:ActionController)
|
||||||
include ActionController::UrlWriter
|
include ActionController::UrlWriter
|
||||||
include ActionController::Layout
|
include ActionController::Layout
|
||||||
|
|
@ -254,6 +254,8 @@ module ActionMailer #:nodoc:
|
||||||
private_class_method :new #:nodoc:
|
private_class_method :new #:nodoc:
|
||||||
|
|
||||||
class_inheritable_accessor :view_paths
|
class_inheritable_accessor :view_paths
|
||||||
|
self.view_paths = []
|
||||||
|
|
||||||
cattr_accessor :logger
|
cattr_accessor :logger
|
||||||
|
|
||||||
@@smtp_settings = {
|
@@smtp_settings = {
|
||||||
|
|
@ -262,7 +264,8 @@ module ActionMailer #:nodoc:
|
||||||
:domain => 'localhost.localdomain',
|
:domain => 'localhost.localdomain',
|
||||||
:user_name => nil,
|
:user_name => nil,
|
||||||
:password => nil,
|
:password => nil,
|
||||||
:authentication => nil
|
:authentication => nil,
|
||||||
|
:enable_starttls_auto => true,
|
||||||
}
|
}
|
||||||
cattr_accessor :smtp_settings
|
cattr_accessor :smtp_settings
|
||||||
|
|
||||||
|
|
@ -426,12 +429,6 @@ module ActionMailer #:nodoc:
|
||||||
new.deliver!(mail)
|
new.deliver!(mail)
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_template_extension(extension)
|
|
||||||
ActiveSupport::Deprecation.warn(
|
|
||||||
"ActionMailer::Base.register_template_extension has been deprecated." +
|
|
||||||
"Use ActionView::Base.register_template_extension instead", caller)
|
|
||||||
end
|
|
||||||
|
|
||||||
def template_root
|
def template_root
|
||||||
self.view_paths && self.view_paths.first
|
self.view_paths && self.view_paths.first
|
||||||
end
|
end
|
||||||
|
|
@ -482,7 +479,7 @@ module ActionMailer #:nodoc:
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
unless @parts.empty?
|
unless @parts.empty?
|
||||||
@content_type = "multipart/alternative"
|
@content_type = "multipart/alternative" if @content_type !~ /^multipart/
|
||||||
@parts = sort_parts(@parts, @implicit_parts_order)
|
@parts = sort_parts(@parts, @implicit_parts_order)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -546,10 +543,16 @@ module ActionMailer #:nodoc:
|
||||||
@headers ||= {}
|
@headers ||= {}
|
||||||
@body ||= {}
|
@body ||= {}
|
||||||
@mime_version = @@default_mime_version.dup if @@default_mime_version
|
@mime_version = @@default_mime_version.dup if @@default_mime_version
|
||||||
|
@sent_on ||= Time.now
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_message(method_name, body)
|
def render_message(method_name, body)
|
||||||
|
if method_name.respond_to?(:content_type)
|
||||||
|
@current_template_content_type = method_name.content_type
|
||||||
|
end
|
||||||
render :file => method_name, :body => body
|
render :file => method_name, :body => body
|
||||||
|
ensure
|
||||||
|
@current_template_content_type = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(opts)
|
def render(opts)
|
||||||
|
|
@ -568,11 +571,17 @@ module ActionMailer #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_template_format
|
def default_template_format
|
||||||
|
if @current_template_content_type
|
||||||
|
Mime::Type.lookup(@current_template_content_type).to_sym
|
||||||
|
else
|
||||||
:html
|
:html
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def candidate_for_layout?(options)
|
def candidate_for_layout?(options)
|
||||||
!@template.send(:_exempt_from_layout?, default_template_name)
|
!self.view_paths.find_template(default_template_name, default_template_format).exempt_from_layout?
|
||||||
|
rescue ActionView::MissingTemplate
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
def template_root
|
def template_root
|
||||||
|
|
@ -588,7 +597,9 @@ module ActionMailer #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize_template_class(assigns)
|
def initialize_template_class(assigns)
|
||||||
ActionView::Base.new(view_paths, assigns, self)
|
template = ActionView::Base.new(self.class.view_paths, assigns, self)
|
||||||
|
template.template_format = default_template_format
|
||||||
|
template
|
||||||
end
|
end
|
||||||
|
|
||||||
def sort_parts(parts, order = [])
|
def sort_parts(parts, order = [])
|
||||||
|
|
@ -637,11 +648,11 @@ module ActionMailer #:nodoc:
|
||||||
|
|
||||||
if @parts.empty?
|
if @parts.empty?
|
||||||
m.set_content_type(real_content_type, nil, ctype_attrs)
|
m.set_content_type(real_content_type, nil, ctype_attrs)
|
||||||
m.body = Utils.normalize_new_lines(body)
|
m.body = normalize_new_lines(body)
|
||||||
else
|
else
|
||||||
if String === body
|
if String === body
|
||||||
part = TMail::Mail.new
|
part = TMail::Mail.new
|
||||||
part.body = Utils.normalize_new_lines(body)
|
part.body = normalize_new_lines(body)
|
||||||
part.set_content_type(real_content_type, nil, ctype_attrs)
|
part.set_content_type(real_content_type, nil, ctype_attrs)
|
||||||
part.set_content_disposition "inline"
|
part.set_content_disposition "inline"
|
||||||
m.parts << part
|
m.parts << part
|
||||||
|
|
@ -664,10 +675,10 @@ module ActionMailer #:nodoc:
|
||||||
def perform_delivery_smtp(mail)
|
def perform_delivery_smtp(mail)
|
||||||
destinations = mail.destinations
|
destinations = mail.destinations
|
||||||
mail.ready_to_send
|
mail.ready_to_send
|
||||||
sender = mail['return-path'] || mail.from
|
sender = (mail['return-path'] && mail['return-path'].spec) || mail['from']
|
||||||
|
|
||||||
smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port])
|
smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port])
|
||||||
smtp.enable_starttls_auto if smtp.respond_to?(:enable_starttls_auto)
|
smtp.enable_starttls_auto if smtp_settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto)
|
||||||
smtp.start(smtp_settings[:domain], smtp_settings[:user_name], smtp_settings[:password],
|
smtp.start(smtp_settings[:domain], smtp_settings[:user_name], smtp_settings[:password],
|
||||||
smtp_settings[:authentication]) do |smtp|
|
smtp_settings[:authentication]) do |smtp|
|
||||||
smtp.sendmail(mail.encoded, sender, destinations)
|
smtp.sendmail(mail.encoded, sender, destinations)
|
||||||
|
|
@ -687,4 +698,9 @@ module ActionMailer #:nodoc:
|
||||||
deliveries << mail
|
deliveries << mail
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Base.class_eval do
|
||||||
|
include Helpers
|
||||||
|
helper MailHelper
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
require 'active_support/dependencies'
|
||||||
|
|
||||||
module ActionMailer
|
module ActionMailer
|
||||||
module Helpers #:nodoc:
|
module Helpers #:nodoc:
|
||||||
def self.included(base) #:nodoc:
|
def self.included(base) #:nodoc:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
require 'text/format'
|
|
||||||
|
|
||||||
module MailHelper
|
module MailHelper
|
||||||
# Uses Text::Format to take the text and format it, indented two spaces for
|
# Uses Text::Format to take the text and format it, indented two spaces for
|
||||||
# each line, and wrapped at 72 columns.
|
# each line, and wrapped at 72 columns.
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
require 'action_mailer/adv_attr_accessor'
|
|
||||||
require 'action_mailer/part_container'
|
|
||||||
require 'action_mailer/utils'
|
|
||||||
|
|
||||||
module ActionMailer
|
module ActionMailer
|
||||||
# Represents a subpart of an email message. It shares many similar
|
# Represents a subpart of an email message. It shares many similar
|
||||||
# attributes of ActionMailer::Base. Although you can create parts manually
|
# attributes of ActionMailer::Base. Although you can create parts manually
|
||||||
# and add them to the +parts+ list of the mailer, it is easier
|
# and add them to the +parts+ list of the mailer, it is easier
|
||||||
# to use the helper methods in ActionMailer::PartContainer.
|
# to use the helper methods in ActionMailer::PartContainer.
|
||||||
class Part
|
class Part
|
||||||
include ActionMailer::AdvAttrAccessor
|
include AdvAttrAccessor, PartContainer, Utils
|
||||||
include ActionMailer::PartContainer
|
|
||||||
|
|
||||||
# Represents the body of the part, as a string. This should not be a
|
# Represents the body of the part, as a string. This should not be a
|
||||||
# Hash (like ActionMailer::Base), but if you want a template to be rendered
|
# Hash (like ActionMailer::Base), but if you want a template to be rendered
|
||||||
|
|
@ -64,7 +59,7 @@ module ActionMailer
|
||||||
when "base64" then
|
when "base64" then
|
||||||
part.body = TMail::Base64.folding_encode(body)
|
part.body = TMail::Base64.folding_encode(body)
|
||||||
when "quoted-printable"
|
when "quoted-printable"
|
||||||
part.body = [Utils.normalize_new_lines(body)].pack("M*")
|
part.body = [normalize_new_lines(body)].pack("M*")
|
||||||
else
|
else
|
||||||
part.body = body
|
part.body = body
|
||||||
end
|
end
|
||||||
|
|
@ -93,7 +88,10 @@ module ActionMailer
|
||||||
part.parts << prt
|
part.parts << prt
|
||||||
end
|
end
|
||||||
|
|
||||||
part.set_content_type(real_content_type, nil, ctype_attrs) if real_content_type =~ /multipart/
|
if real_content_type =~ /multipart/
|
||||||
|
ctype_attrs.delete 'charset'
|
||||||
|
part.set_content_type(real_content_type, nil, ctype_attrs)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
headers.each { |k,v| part[k] = v }
|
headers.each { |k,v| part[k] = v }
|
||||||
|
|
@ -102,7 +100,6 @@ module ActionMailer
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def squish(values={})
|
def squish(values={})
|
||||||
values.delete_if { |k,v| v.nil? }
|
values.delete_if { |k,v| v.nil? }
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,11 @@ module ActionMailer
|
||||||
private
|
private
|
||||||
|
|
||||||
def parse_content_type(defaults=nil)
|
def parse_content_type(defaults=nil)
|
||||||
return [defaults && defaults.content_type, {}] if content_type.blank?
|
if content_type.blank?
|
||||||
|
return defaults ?
|
||||||
|
[ defaults.content_type, { 'charset' => defaults.charset } ] :
|
||||||
|
[ nil, {} ]
|
||||||
|
end
|
||||||
ctype, *attrs = content_type.split(/;\s*/)
|
ctype, *attrs = content_type.split(/;\s*/)
|
||||||
attrs = attrs.inject({}) { |h,s| k,v = s.split(/=/, 2); h[k] = v; h }
|
attrs = attrs.inject({}) { |h,s| k,v = s.split(/=/, 2); h[k] = v; h }
|
||||||
[ctype, {"charset" => charset || defaults && defaults.charset}.merge(attrs)]
|
[ctype, {"charset" => charset || defaults && defaults.charset}.merge(attrs)]
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ module ActionMailer
|
||||||
# account multi-byte characters (if executing with $KCODE="u", for instance)
|
# account multi-byte characters (if executing with $KCODE="u", for instance)
|
||||||
def quoted_printable_encode(character)
|
def quoted_printable_encode(character)
|
||||||
result = ""
|
result = ""
|
||||||
character.each_byte { |b| result << "=%02x" % b }
|
character.each_byte { |b| result << "=%02X" % b }
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ module ActionMailer
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestCase < ActiveSupport::TestCase
|
class TestCase < ActiveSupport::TestCase
|
||||||
include ActionMailer::Quoting
|
include Quoting, TestHelper
|
||||||
|
|
||||||
setup :initialize_test_deliveries
|
setup :initialize_test_deliveries
|
||||||
setup :set_expected_mail
|
setup :set_expected_mail
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ module ActionMailer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Deprecate this
|
||||||
module Test
|
module Test
|
||||||
module Unit
|
module Unit
|
||||||
class TestCase
|
class TestCase
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,5 @@ module ActionMailer
|
||||||
def normalize_new_lines(text)
|
def normalize_new_lines(text)
|
||||||
text.to_s.gsub(/\r\n?/, "\n")
|
text.to_s.gsub(/\r\n?/, "\n")
|
||||||
end
|
end
|
||||||
module_function :normalize_new_lines
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
# Prefer gems to the bundled libs.
|
|
||||||
require 'rubygems'
|
|
||||||
|
|
||||||
begin
|
|
||||||
gem 'tmail', '~> 1.2.3'
|
|
||||||
rescue Gem::LoadError
|
|
||||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/tmail-1.2.3"
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
gem 'text-format', '>= 0.6.3'
|
|
||||||
rescue Gem::LoadError
|
|
||||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/text-format-0.6.3"
|
|
||||||
end
|
|
||||||
|
|
@ -1150,7 +1150,7 @@ if __FILE__ == $0
|
||||||
assert_equal(Text::Format::JUSTIFY, @format_o.format_style)
|
assert_equal(Text::Format::JUSTIFY, @format_o.format_style)
|
||||||
assert_match(/^of freedom, and that government of the people, by the people, for the$/,
|
assert_match(/^of freedom, and that government of the people, by the people, for the$/,
|
||||||
@format_o.format(GETTYSBURG).split("\n")[-3])
|
@format_o.format(GETTYSBURG).split("\n")[-3])
|
||||||
assert_raises(ArgumentError) { @format_o.format_style = 33 }
|
assert_raise(ArgumentError) { @format_o.format_style = 33 }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_tag_paragraph
|
def test_tag_paragraph
|
||||||
|
|
|
||||||
10
vendor/rails/actionmailer/lib/action_mailer/vendor/text_format.rb
vendored
Normal file
10
vendor/rails/actionmailer/lib/action_mailer/vendor/text_format.rb
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Prefer gems to the bundled libs.
|
||||||
|
require 'rubygems'
|
||||||
|
|
||||||
|
begin
|
||||||
|
gem 'text-format', '>= 0.6.3'
|
||||||
|
rescue Gem::LoadError
|
||||||
|
$:.unshift "#{File.dirname(__FILE__)}/text-format-0.6.3"
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'text/format'
|
||||||
|
|
@ -43,6 +43,7 @@ module Racc
|
||||||
|
|
||||||
class Parser
|
class Parser
|
||||||
|
|
||||||
|
old_verbose, $VERBOSE = $VERBOSE, nil
|
||||||
Racc_Runtime_Version = '1.4.5'
|
Racc_Runtime_Version = '1.4.5'
|
||||||
Racc_Runtime_Revision = '$Revision: 1.7 $'.split[1]
|
Racc_Runtime_Revision = '$Revision: 1.7 $'.split[1]
|
||||||
|
|
||||||
|
|
@ -71,6 +72,7 @@ module Racc
|
||||||
Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_R
|
Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_R
|
||||||
Racc_Runtime_Type = 'ruby'
|
Racc_Runtime_Type = 'ruby'
|
||||||
end
|
end
|
||||||
|
$VERBOSE = old_verbose
|
||||||
|
|
||||||
def Parser.racc_runtime_type
|
def Parser.racc_runtime_type
|
||||||
Racc_Runtime_Type
|
Racc_Runtime_Type
|
||||||
|
|
|
||||||
17
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail.rb
vendored
Normal file
17
vendor/rails/actionmailer/lib/action_mailer/vendor/tmail.rb
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Prefer gems to the bundled libs.
|
||||||
|
require 'rubygems'
|
||||||
|
|
||||||
|
begin
|
||||||
|
gem 'tmail', '~> 1.2.3'
|
||||||
|
rescue Gem::LoadError
|
||||||
|
$:.unshift "#{File.dirname(__FILE__)}/tmail-1.2.3"
|
||||||
|
end
|
||||||
|
|
||||||
|
module TMail
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'tmail'
|
||||||
|
|
||||||
|
silence_warnings do
|
||||||
|
TMail::Encoder.const_set("MAX_LINE_LEN", 200)
|
||||||
|
end
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
module ActionMailer
|
module ActionMailer
|
||||||
module VERSION #:nodoc:
|
module VERSION #:nodoc:
|
||||||
MAJOR = 2
|
MAJOR = 2
|
||||||
MINOR = 2
|
MINOR = 3
|
||||||
TINY = 2
|
TINY = 5
|
||||||
|
|
||||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
require 'action_mailer'
|
require 'action_mailer'
|
||||||
|
ActiveSupport::Deprecation.warn 'require "actionmailer" is deprecated and will be removed in Rails 3. Use require "action_mailer" instead.'
|
||||||
|
|
|
||||||
18
vendor/rails/actionmailer/test/abstract_unit.rb
vendored
18
vendor/rails/actionmailer/test/abstract_unit.rb
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
|
require 'rubygems'
|
||||||
require 'test/unit'
|
require 'test/unit'
|
||||||
|
|
||||||
$:.unshift "#{File.dirname(__FILE__)}/../lib"
|
$:.unshift "#{File.dirname(__FILE__)}/../lib"
|
||||||
|
|
@ -9,8 +10,15 @@ require 'action_mailer/test_case'
|
||||||
# Show backtraces for deprecated behavior for quicker cleanup.
|
# Show backtraces for deprecated behavior for quicker cleanup.
|
||||||
ActiveSupport::Deprecation.debug = true
|
ActiveSupport::Deprecation.debug = true
|
||||||
|
|
||||||
|
# Bogus template processors
|
||||||
|
ActionView::Template.register_template_handler :haml, lambda { |template| "Look its HAML!".inspect }
|
||||||
|
ActionView::Template.register_template_handler :bak, lambda { |template| "Lame backup".inspect }
|
||||||
|
|
||||||
$:.unshift "#{File.dirname(__FILE__)}/fixtures/helpers"
|
$:.unshift "#{File.dirname(__FILE__)}/fixtures/helpers"
|
||||||
ActionMailer::Base.template_root = "#{File.dirname(__FILE__)}/fixtures"
|
|
||||||
|
ActionView::Base.cache_template_loading = true
|
||||||
|
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
|
||||||
|
ActionMailer::Base.template_root = FIXTURE_LOAD_PATH
|
||||||
|
|
||||||
class MockSMTP
|
class MockSMTP
|
||||||
def self.deliveries
|
def self.deliveries
|
||||||
|
|
@ -37,7 +45,6 @@ class Net::SMTP
|
||||||
end
|
end
|
||||||
|
|
||||||
def uses_gem(gem_name, test_name, version = '> 0')
|
def uses_gem(gem_name, test_name, version = '> 0')
|
||||||
require 'rubygems'
|
|
||||||
gem gem_name.to_s, version
|
gem gem_name.to_s, version
|
||||||
require gem_name.to_s
|
require gem_name.to_s
|
||||||
yield
|
yield
|
||||||
|
|
@ -45,13 +52,6 @@ rescue LoadError
|
||||||
$stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
|
$stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
|
||||||
end
|
end
|
||||||
|
|
||||||
# Wrap tests that use Mocha and skip if unavailable.
|
|
||||||
unless defined? uses_mocha
|
|
||||||
def uses_mocha(test_name, &block)
|
|
||||||
uses_gem('mocha', test_name, '>= 0.5.5', &block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_delivery_method(delivery_method)
|
def set_delivery_method(delivery_method)
|
||||||
@old_delivery_method = ActionMailer::Base.delivery_method
|
@old_delivery_method = ActionMailer::Base.delivery_method
|
||||||
ActionMailer::Base.delivery_method = delivery_method
|
ActionMailer::Base.delivery_method = delivery_method
|
||||||
|
|
|
||||||
54
vendor/rails/actionmailer/test/asset_host_test.rb
vendored
Normal file
54
vendor/rails/actionmailer/test/asset_host_test.rb
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
require 'abstract_unit'
|
||||||
|
|
||||||
|
class AssetHostMailer < ActionMailer::Base
|
||||||
|
def email_with_asset(recipient)
|
||||||
|
recipients recipient
|
||||||
|
subject "testing email containing asset path while asset_host is set"
|
||||||
|
from "tester@example.com"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class AssetHostTest < Test::Unit::TestCase
|
||||||
|
def setup
|
||||||
|
set_delivery_method :test
|
||||||
|
ActionMailer::Base.perform_deliveries = true
|
||||||
|
ActionMailer::Base.deliveries = []
|
||||||
|
|
||||||
|
@recipient = 'test@localhost'
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
restore_delivery_method
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_asset_host_as_string
|
||||||
|
ActionController::Base.asset_host = "http://www.example.com"
|
||||||
|
mail = AssetHostMailer.deliver_email_with_asset(@recipient)
|
||||||
|
assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_asset_host_as_one_arguement_proc
|
||||||
|
ActionController::Base.asset_host = Proc.new { |source|
|
||||||
|
if source.starts_with?('/images')
|
||||||
|
"http://images.example.com"
|
||||||
|
else
|
||||||
|
"http://assets.example.com"
|
||||||
|
end
|
||||||
|
}
|
||||||
|
mail = AssetHostMailer.deliver_email_with_asset(@recipient)
|
||||||
|
assert_equal "<img alt=\"Somelogo\" src=\"http://images.example.com/images/somelogo.png\" />", mail.body.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_asset_host_as_two_arguement_proc
|
||||||
|
ActionController::Base.asset_host = Proc.new {|source,request|
|
||||||
|
if request && request.ssl?
|
||||||
|
"https://www.example.com"
|
||||||
|
else
|
||||||
|
"http://www.example.com"
|
||||||
|
end
|
||||||
|
}
|
||||||
|
mail = nil
|
||||||
|
assert_nothing_raised { mail = AssetHostMailer.deliver_email_with_asset(@recipient) }
|
||||||
|
assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.strip
|
||||||
|
end
|
||||||
|
end
|
||||||
1
vendor/rails/actionmailer/test/fixtures/asset_host_mailer/email_with_asset.html.erb
vendored
Normal file
1
vendor/rails/actionmailer/test/fixtures/asset_host_mailer/email_with_asset.html.erb
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<%= image_tag "somelogo.png" %>
|
||||||
1
vendor/rails/actionmailer/test/fixtures/auto_layout_mailer/multipart.text.html.erb
vendored
Normal file
1
vendor/rails/actionmailer/test/fixtures/auto_layout_mailer/multipart.text.html.erb
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
text/html multipart
|
||||||
1
vendor/rails/actionmailer/test/fixtures/auto_layout_mailer/multipart.text.plain.erb
vendored
Normal file
1
vendor/rails/actionmailer/test/fixtures/auto_layout_mailer/multipart.text.plain.erb
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
text/plain multipart
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
module ExampleHelper
|
module ExampleHelper
|
||||||
def example_format(text)
|
def example_format(text)
|
||||||
"<em><strong><small>#{text}</small></strong></em>"
|
"<em><strong><small>#{h(text)}</small></strong></em>".html_safe!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
1
vendor/rails/actionmailer/test/fixtures/layouts/auto_layout_mailer.text.erb
vendored
Normal file
1
vendor/rails/actionmailer/test/fixtures/layouts/auto_layout_mailer.text.erb
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
text/plain layout - <%= yield %>
|
||||||
|
|
@ -20,6 +20,14 @@ class AutoLayoutMailer < ActionMailer::Base
|
||||||
from "tester@example.com"
|
from "tester@example.com"
|
||||||
body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" })
|
body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def multipart(recipient, type = nil)
|
||||||
|
recipients recipient
|
||||||
|
subject "You have a mail"
|
||||||
|
from "tester@example.com"
|
||||||
|
|
||||||
|
content_type(type) if type
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ExplicitLayoutMailer < ActionMailer::Base
|
class ExplicitLayoutMailer < ActionMailer::Base
|
||||||
|
|
@ -56,6 +64,43 @@ class LayoutMailerTest < Test::Unit::TestCase
|
||||||
assert_equal "Hello from layout Inside", mail.body.strip
|
assert_equal "Hello from layout Inside", mail.body.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_should_pickup_multipart_layout
|
||||||
|
mail = AutoLayoutMailer.create_multipart(@recipient)
|
||||||
|
assert_equal "multipart/alternative", mail.content_type
|
||||||
|
assert_equal 2, mail.parts.size
|
||||||
|
|
||||||
|
assert_equal 'text/plain', mail.parts.first.content_type
|
||||||
|
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
|
||||||
|
|
||||||
|
assert_equal 'text/html', mail.parts.last.content_type
|
||||||
|
assert_equal "Hello from layout text/html multipart", mail.parts.last.body
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_should_pickup_multipartmixed_layout
|
||||||
|
mail = AutoLayoutMailer.create_multipart(@recipient, "multipart/mixed")
|
||||||
|
assert_equal "multipart/mixed", mail.content_type
|
||||||
|
assert_equal 2, mail.parts.size
|
||||||
|
|
||||||
|
assert_equal 'text/plain', mail.parts.first.content_type
|
||||||
|
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
|
||||||
|
|
||||||
|
assert_equal 'text/html', mail.parts.last.content_type
|
||||||
|
assert_equal "Hello from layout text/html multipart", mail.parts.last.body
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_should_fix_multipart_layout
|
||||||
|
mail = AutoLayoutMailer.create_multipart(@recipient, "text/plain")
|
||||||
|
assert_equal "multipart/alternative", mail.content_type
|
||||||
|
assert_equal 2, mail.parts.size
|
||||||
|
|
||||||
|
assert_equal 'text/plain', mail.parts.first.content_type
|
||||||
|
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
|
||||||
|
|
||||||
|
assert_equal 'text/html', mail.parts.last.content_type
|
||||||
|
assert_equal "Hello from layout text/html multipart", mail.parts.last.body
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def test_should_pickup_layout_given_to_render
|
def test_should_pickup_layout_given_to_render
|
||||||
mail = AutoLayoutMailer.create_spam(@recipient)
|
mail = AutoLayoutMailer.create_spam(@recipient)
|
||||||
assert_equal "Spammer layout Hello, Earth", mail.body.strip
|
assert_equal "Spammer layout Hello, Earth", mail.body.strip
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ class TestMailer < ActionMailer::Base
|
||||||
@recipients = recipient
|
@recipients = recipient
|
||||||
@subject = "[Signed up] Welcome #{recipient}"
|
@subject = "[Signed up] Welcome #{recipient}"
|
||||||
@from = "system@loudthinking.com"
|
@from = "system@loudthinking.com"
|
||||||
@sent_on = Time.local(2004, 12, 12)
|
|
||||||
@body["recipient"] = recipient
|
@body["recipient"] = recipient
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -289,8 +288,6 @@ class TestMailer < ActionMailer::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
uses_mocha 'ActionMailerTest' do
|
|
||||||
|
|
||||||
class ActionMailerTest < Test::Unit::TestCase
|
class ActionMailerTest < Test::Unit::TestCase
|
||||||
include ActionMailer::Quoting
|
include ActionMailer::Quoting
|
||||||
|
|
||||||
|
|
@ -332,6 +329,7 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||||
assert_equal "multipart/mixed", created.content_type
|
assert_equal "multipart/mixed", created.content_type
|
||||||
assert_equal "multipart/alternative", created.parts.first.content_type
|
assert_equal "multipart/alternative", created.parts.first.content_type
|
||||||
assert_equal "bar", created.parts.first.header['foo'].to_s
|
assert_equal "bar", created.parts.first.header['foo'].to_s
|
||||||
|
assert_nil created.parts.first.charset
|
||||||
assert_equal "text/plain", created.parts.first.parts.first.content_type
|
assert_equal "text/plain", created.parts.first.parts.first.content_type
|
||||||
assert_equal "text/html", created.parts.first.parts[1].content_type
|
assert_equal "text/html", created.parts.first.parts[1].content_type
|
||||||
assert_equal "application/octet-stream", created.parts[1].content_type
|
assert_equal "application/octet-stream", created.parts[1].content_type
|
||||||
|
|
@ -357,12 +355,14 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_signed_up
|
def test_signed_up
|
||||||
|
Time.stubs(:now => Time.now)
|
||||||
|
|
||||||
expected = new_mail
|
expected = new_mail
|
||||||
expected.to = @recipient
|
expected.to = @recipient
|
||||||
expected.subject = "[Signed up] Welcome #{@recipient}"
|
expected.subject = "[Signed up] Welcome #{@recipient}"
|
||||||
expected.body = "Hello there, \n\nMr. #{@recipient}"
|
expected.body = "Hello there, \n\nMr. #{@recipient}"
|
||||||
expected.from = "system@loudthinking.com"
|
expected.from = "system@loudthinking.com"
|
||||||
expected.date = Time.local(2004, 12, 12)
|
expected.date = Time.now
|
||||||
|
|
||||||
created = nil
|
created = nil
|
||||||
assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
|
assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
|
||||||
|
|
@ -389,6 +389,8 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_custom_templating_extension
|
def test_custom_templating_extension
|
||||||
|
assert ActionView::Template.template_handler_extensions.include?("haml"), "haml extension was not registered"
|
||||||
|
|
||||||
# N.b., custom_templating_extension.text.plain.haml is expected to be in fixtures/test_mailer directory
|
# N.b., custom_templating_extension.text.plain.haml is expected to be in fixtures/test_mailer directory
|
||||||
expected = new_mail
|
expected = new_mail
|
||||||
expected.to = @recipient
|
expected.to = @recipient
|
||||||
|
|
@ -568,7 +570,9 @@ class ActionMailerTest < Test::Unit::TestCase
|
||||||
mail = TestMailer.create_signed_up(@recipient)
|
mail = TestMailer.create_signed_up(@recipient)
|
||||||
logger = mock()
|
logger = mock()
|
||||||
logger.expects(:info).with("Sent mail to #{@recipient}")
|
logger.expects(:info).with("Sent mail to #{@recipient}")
|
||||||
logger.expects(:debug).with("\n#{mail.encoded}")
|
logger.expects(:debug).with() do |logged_text|
|
||||||
|
logged_text =~ /\[Signed up\] Welcome/
|
||||||
|
end
|
||||||
TestMailer.logger = logger
|
TestMailer.logger = logger
|
||||||
TestMailer.deliver_signed_up(@recipient)
|
TestMailer.deliver_signed_up(@recipient)
|
||||||
end
|
end
|
||||||
|
|
@ -799,6 +803,8 @@ EOF
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_implicitly_multipart_messages
|
def test_implicitly_multipart_messages
|
||||||
|
assert ActionView::Template.template_handler_extensions.include?("bak"), "bak extension was not registered"
|
||||||
|
|
||||||
mail = TestMailer.create_implicitly_multipart_example(@recipient)
|
mail = TestMailer.create_implicitly_multipart_example(@recipient)
|
||||||
assert_equal 3, mail.parts.length
|
assert_equal 3, mail.parts.length
|
||||||
assert_equal "1.0", mail.mime_version
|
assert_equal "1.0", mail.mime_version
|
||||||
|
|
@ -812,6 +818,8 @@ EOF
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_implicitly_multipart_messages_with_custom_order
|
def test_implicitly_multipart_messages_with_custom_order
|
||||||
|
assert ActionView::Template.template_handler_extensions.include?("bak"), "bak extension was not registered"
|
||||||
|
|
||||||
mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["text/yaml", "text/plain"])
|
mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["text/yaml", "text/plain"])
|
||||||
assert_equal 3, mail.parts.length
|
assert_equal 3, mail.parts.length
|
||||||
assert_equal "text/html", mail.parts[0].content_type
|
assert_equal "text/html", mail.parts[0].content_type
|
||||||
|
|
@ -915,6 +923,8 @@ EOF
|
||||||
def test_multipart_with_template_path_with_dots
|
def test_multipart_with_template_path_with_dots
|
||||||
mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
|
mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
|
||||||
assert_equal 2, mail.parts.length
|
assert_equal 2, mail.parts.length
|
||||||
|
assert_equal 'text/plain', mail.parts[0].content_type
|
||||||
|
assert_equal 'utf-8', mail.parts[0].charset
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_custom_content_type_attributes
|
def test_custom_content_type_attributes
|
||||||
|
|
@ -932,6 +942,7 @@ EOF
|
||||||
ActionMailer::Base.delivery_method = :smtp
|
ActionMailer::Base.delivery_method = :smtp
|
||||||
TestMailer.deliver_return_path
|
TestMailer.deliver_return_path
|
||||||
assert_match %r{^Return-Path: <another@somewhere.test>}, MockSMTP.deliveries[0][0]
|
assert_match %r{^Return-Path: <another@somewhere.test>}, MockSMTP.deliveries[0][0]
|
||||||
|
assert_equal "another@somewhere.test", MockSMTP.deliveries[0][1].to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_body_is_stored_as_an_ivar
|
def test_body_is_stored_as_an_ivar
|
||||||
|
|
@ -940,6 +951,7 @@ EOF
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_starttls_is_enabled_if_supported
|
def test_starttls_is_enabled_if_supported
|
||||||
|
ActionMailer::Base.smtp_settings[:enable_starttls_auto] = true
|
||||||
MockSMTP.any_instance.expects(:respond_to?).with(:enable_starttls_auto).returns(true)
|
MockSMTP.any_instance.expects(:respond_to?).with(:enable_starttls_auto).returns(true)
|
||||||
MockSMTP.any_instance.expects(:enable_starttls_auto)
|
MockSMTP.any_instance.expects(:enable_starttls_auto)
|
||||||
ActionMailer::Base.delivery_method = :smtp
|
ActionMailer::Base.delivery_method = :smtp
|
||||||
|
|
@ -947,25 +959,34 @@ EOF
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_starttls_is_disabled_if_not_supported
|
def test_starttls_is_disabled_if_not_supported
|
||||||
|
ActionMailer::Base.smtp_settings[:enable_starttls_auto] = true
|
||||||
MockSMTP.any_instance.expects(:respond_to?).with(:enable_starttls_auto).returns(false)
|
MockSMTP.any_instance.expects(:respond_to?).with(:enable_starttls_auto).returns(false)
|
||||||
MockSMTP.any_instance.expects(:enable_starttls_auto).never
|
MockSMTP.any_instance.expects(:enable_starttls_auto).never
|
||||||
ActionMailer::Base.delivery_method = :smtp
|
ActionMailer::Base.delivery_method = :smtp
|
||||||
TestMailer.deliver_signed_up(@recipient)
|
TestMailer.deliver_signed_up(@recipient)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
end # uses_mocha
|
def test_starttls_is_not_enabled
|
||||||
|
ActionMailer::Base.smtp_settings[:enable_starttls_auto] = false
|
||||||
|
MockSMTP.any_instance.expects(:respond_to?).never
|
||||||
|
MockSMTP.any_instance.expects(:enable_starttls_auto).never
|
||||||
|
ActionMailer::Base.delivery_method = :smtp
|
||||||
|
TestMailer.deliver_signed_up(@recipient)
|
||||||
|
ensure
|
||||||
|
ActionMailer::Base.smtp_settings[:enable_starttls_auto] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class InheritableTemplateRootTest < Test::Unit::TestCase
|
class InheritableTemplateRootTest < Test::Unit::TestCase
|
||||||
def test_attr
|
def test_attr
|
||||||
expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
|
expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
|
||||||
assert_equal expected, FunkyPathMailer.template_root
|
assert_equal expected, FunkyPathMailer.template_root.to_s
|
||||||
|
|
||||||
sub = Class.new(FunkyPathMailer)
|
sub = Class.new(FunkyPathMailer)
|
||||||
sub.template_root = 'test/path'
|
sub.template_root = 'test/path'
|
||||||
|
|
||||||
assert_equal 'test/path', sub.template_root
|
assert_equal 'test/path', sub.template_root.to_s
|
||||||
assert_equal expected, FunkyPathMailer.template_root
|
assert_equal expected, FunkyPathMailer.template_root.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1051,7 +1072,7 @@ class RespondToTest < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_should_still_raise_exception_with_expected_message_when_calling_an_undefined_method
|
def test_should_still_raise_exception_with_expected_message_when_calling_an_undefined_method
|
||||||
error = assert_raises NoMethodError do
|
error = assert_raise NoMethodError do
|
||||||
RespondToMailer.not_a_method
|
RespondToMailer.not_a_method
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
require 'abstract_unit'
|
require 'abstract_unit'
|
||||||
require 'tmail'
|
|
||||||
require 'tempfile'
|
require 'tempfile'
|
||||||
|
|
||||||
class QuotingTest < Test::Unit::TestCase
|
class QuotingTest < Test::Unit::TestCase
|
||||||
|
|
@ -49,8 +48,10 @@ class QuotingTest < Test::Unit::TestCase
|
||||||
|
|
||||||
result = execute_in_sandbox(<<-CODE)
|
result = execute_in_sandbox(<<-CODE)
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
||||||
|
if RUBY_VERSION < '1.9'
|
||||||
$KCODE = 'u'
|
$KCODE = 'u'
|
||||||
require 'jcode'
|
require 'jcode'
|
||||||
|
end
|
||||||
require 'action_mailer/quoting'
|
require 'action_mailer/quoting'
|
||||||
include ActionMailer::Quoting
|
include ActionMailer::Quoting
|
||||||
quoted_printable(#{original.inspect}, "UTF-8")
|
quoted_printable(#{original.inspect}, "UTF-8")
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class TestHelperMailerTest < ActionMailer::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_determine_default_mailer_raises_correct_error
|
def test_determine_default_mailer_raises_correct_error
|
||||||
assert_raises(ActionMailer::NonInferrableMailerError) do
|
assert_raise(ActionMailer::NonInferrableMailerError) do
|
||||||
self.class.determine_default_mailer("NotAMailerTest")
|
self.class.determine_default_mailer("NotAMailerTest")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -36,7 +36,7 @@ class TestHelperMailerTest < ActionMailer::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_encode
|
def test_encode
|
||||||
assert_equal "=?utf-8?Q?=0aasdf=0a?=", encode("\nasdf\n")
|
assert_equal "=?utf-8?Q?=0Aasdf=0A?=", encode("\nasdf\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_assert_emails
|
def test_assert_emails
|
||||||
|
|
@ -84,7 +84,7 @@ class TestHelperMailerTest < ActionMailer::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_assert_emails_too_few_sent
|
def test_assert_emails_too_few_sent
|
||||||
error = assert_raises Test::Unit::AssertionFailedError do
|
error = assert_raise ActiveSupport::TestCase::Assertion do
|
||||||
assert_emails 2 do
|
assert_emails 2 do
|
||||||
TestHelperMailer.deliver_test
|
TestHelperMailer.deliver_test
|
||||||
end
|
end
|
||||||
|
|
@ -94,7 +94,7 @@ class TestHelperMailerTest < ActionMailer::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_assert_emails_too_many_sent
|
def test_assert_emails_too_many_sent
|
||||||
error = assert_raises Test::Unit::AssertionFailedError do
|
error = assert_raise ActiveSupport::TestCase::Assertion do
|
||||||
assert_emails 1 do
|
assert_emails 1 do
|
||||||
TestHelperMailer.deliver_test
|
TestHelperMailer.deliver_test
|
||||||
TestHelperMailer.deliver_test
|
TestHelperMailer.deliver_test
|
||||||
|
|
@ -105,7 +105,7 @@ class TestHelperMailerTest < ActionMailer::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_assert_no_emails_failure
|
def test_assert_no_emails_failure
|
||||||
error = assert_raises Test::Unit::AssertionFailedError do
|
error = assert_raise ActiveSupport::TestCase::Assertion do
|
||||||
assert_no_emails do
|
assert_no_emails do
|
||||||
TestHelperMailer.deliver_test
|
TestHelperMailer.deliver_test
|
||||||
end
|
end
|
||||||
|
|
|
||||||
888
vendor/rails/actionpack/CHANGELOG
vendored
888
vendor/rails/actionpack/CHANGELOG
vendored
File diff suppressed because it is too large
Load diff
2
vendor/rails/actionpack/MIT-LICENSE
vendored
2
vendor/rails/actionpack/MIT-LICENSE
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2004-2008 David Heinemeier Hansson
|
Copyright (c) 2004-2009 David Heinemeier Hansson
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
a copy of this software and associated documentation files (the
|
a copy of this software and associated documentation files (the
|
||||||
|
|
|
||||||
94
vendor/rails/actionpack/README
vendored
94
vendor/rails/actionpack/README
vendored
|
|
@ -10,7 +10,7 @@ Action Pack implements these actions as public methods on Action Controllers
|
||||||
and uses Action Views to implement the template rendering. Action Controllers
|
and uses Action Views to implement the template rendering. Action Controllers
|
||||||
are then responsible for handling all the actions relating to a certain part
|
are then responsible for handling all the actions relating to a certain part
|
||||||
of an application. This grouping usually consists of actions for lists and for
|
of an application. This grouping usually consists of actions for lists and for
|
||||||
CRUDs revolving around a single (or a few) model objects. So ContactController
|
CRUDs revolving around a single (or a few) model objects. So ContactsController
|
||||||
would be responsible for listing contacts, creating, deleting, and updating
|
would be responsible for listing contacts, creating, deleting, and updating
|
||||||
contacts. A WeblogController could be responsible for both posts and comments.
|
contacts. A WeblogController could be responsible for both posts and comments.
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ A short rundown of the major features:
|
||||||
* Actions grouped in controller as methods instead of separate command objects
|
* Actions grouped in controller as methods instead of separate command objects
|
||||||
and can therefore share helper methods
|
and can therefore share helper methods
|
||||||
|
|
||||||
BlogController < ActionController::Base
|
CustomersController < ActionController::Base
|
||||||
def show
|
def show
|
||||||
@customer = find_customer
|
@customer = find_customer
|
||||||
end
|
end
|
||||||
|
|
@ -42,7 +42,7 @@ A short rundown of the major features:
|
||||||
@customer = find_customer
|
@customer = find_customer
|
||||||
@customer.attributes = params[:customer]
|
@customer.attributes = params[:customer]
|
||||||
@customer.save ?
|
@customer.save ?
|
||||||
redirect_to(:action => "display") :
|
redirect_to(:action => "show") :
|
||||||
render(:action => "edit")
|
render(:action => "edit")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ A short rundown of the major features:
|
||||||
Title: <%= post.title %>
|
Title: <%= post.title %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
All post titles: <%= @post.collect{ |p| p.title }.join ", " %>
|
All post titles: <%= @posts.collect{ |p| p.title }.join ", " %>
|
||||||
|
|
||||||
<% unless @person.is_client? %>
|
<% unless @person.is_client? %>
|
||||||
Not for clients to see...
|
Not for clients to see...
|
||||||
|
|
@ -123,7 +123,7 @@ A short rundown of the major features:
|
||||||
<%= text_field "post", "title", "size" => 30 %>
|
<%= text_field "post", "title", "size" => 30 %>
|
||||||
<%= html_date_select(Date.today) %>
|
<%= html_date_select(Date.today) %>
|
||||||
<%= link_to "New post", :controller => "post", :action => "new" %>
|
<%= link_to "New post", :controller => "post", :action => "new" %>
|
||||||
<%= truncate(post.title, 25) %>
|
<%= truncate(post.title, :length => 25) %>
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionView/Helpers.html]
|
{Learn more}[link:classes/ActionView/Helpers.html]
|
||||||
|
|
||||||
|
|
@ -177,21 +177,6 @@ A short rundown of the major features:
|
||||||
{Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
|
{Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
|
||||||
|
|
||||||
|
|
||||||
* Pagination for navigating lists of results
|
|
||||||
|
|
||||||
# controller
|
|
||||||
def list
|
|
||||||
@pages, @people =
|
|
||||||
paginate :people, :order => 'last_name, first_name'
|
|
||||||
end
|
|
||||||
|
|
||||||
# view
|
|
||||||
<%= link_to "Previous page", { :page => @pages.current.previous } if @pages.current.previous %>
|
|
||||||
<%= link_to "Next page", { :page => @pages.current.next } if @pages.current.next %>
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionController/Pagination.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Easy testing of both controller and rendered template through ActionController::TestCase
|
* Easy testing of both controller and rendered template through ActionController::TestCase
|
||||||
|
|
||||||
class LoginControllerTest < ActionController::TestCase
|
class LoginControllerTest < ActionController::TestCase
|
||||||
|
|
@ -215,11 +200,11 @@ A short rundown of the major features:
|
||||||
If Active Record is used as the model, you'll have the database debugging
|
If Active Record is used as the model, you'll have the database debugging
|
||||||
as well:
|
as well:
|
||||||
|
|
||||||
Processing WeblogController#create (for 127.0.0.1 at Sat Jun 19 14:04:23)
|
Processing PostsController#create (for 127.0.0.1 at Sat Jun 19 14:04:23)
|
||||||
Params: {"controller"=>"weblog", "action"=>"create",
|
Params: {"controller"=>"posts", "action"=>"create",
|
||||||
"post"=>{"title"=>"this is good"} }
|
"post"=>{"title"=>"this is good"} }
|
||||||
SQL (0.000627) INSERT INTO posts (title) VALUES('this is good')
|
SQL (0.000627) INSERT INTO posts (title) VALUES('this is good')
|
||||||
Redirected to http://test/weblog/display/5
|
Redirected to http://example.com/posts/5
|
||||||
Completed in 0.221764 (4 reqs/sec) | DB: 0.059920 (27%)
|
Completed in 0.221764 (4 reqs/sec) | DB: 0.059920 (27%)
|
||||||
|
|
||||||
You specify a logger through a class method, such as:
|
You specify a logger through a class method, such as:
|
||||||
|
|
@ -256,30 +241,6 @@ A short rundown of the major features:
|
||||||
{Learn more}[link:classes/ActionController/Caching.html]
|
{Learn more}[link:classes/ActionController/Caching.html]
|
||||||
|
|
||||||
|
|
||||||
* Component requests from one controller to another
|
|
||||||
|
|
||||||
class WeblogController < ActionController::Base
|
|
||||||
# Performs a method and then lets hello_world output its render
|
|
||||||
def delegate_action
|
|
||||||
do_other_stuff_before_hello_world
|
|
||||||
render_component :controller => "greeter", :action => "hello_world"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class GreeterController < ActionController::Base
|
|
||||||
def hello_world
|
|
||||||
render_text "Hello World!"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
The same can be done in a view to do a partial rendering:
|
|
||||||
|
|
||||||
Let's see a greeting:
|
|
||||||
<%= render_component :controller => "greeter", :action => "hello_world" %>
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionController/Components.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Powerful debugging mechanism for local requests
|
* Powerful debugging mechanism for local requests
|
||||||
|
|
||||||
All exceptions raised on actions performed on the request of a local user
|
All exceptions raised on actions performed on the request of a local user
|
||||||
|
|
@ -336,7 +297,7 @@ A short rundown of the major features:
|
||||||
class WeblogController < ActionController::Base
|
class WeblogController < ActionController::Base
|
||||||
def create
|
def create
|
||||||
post = Post.create(params[:post])
|
post = Post.create(params[:post])
|
||||||
redirect_to :action => "display", :id => post.id
|
redirect_to :action => "show", :id => post.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -362,7 +323,7 @@ methods:
|
||||||
@posts = Post.find(:all)
|
@posts = Post.find(:all)
|
||||||
end
|
end
|
||||||
|
|
||||||
def display
|
def show
|
||||||
@post = Post.find(params[:id])
|
@post = Post.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -372,7 +333,7 @@ methods:
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@post = Post.create(params[:post])
|
@post = Post.create(params[:post])
|
||||||
redirect_to :action => "display", :id => @post.id
|
redirect_to :action => "show", :id => @post.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -385,48 +346,33 @@ request from the web-server (like to be Apache).
|
||||||
|
|
||||||
And the templates look like this:
|
And the templates look like this:
|
||||||
|
|
||||||
weblog/layout.erb:
|
weblog/layout.html.erb:
|
||||||
<html><body>
|
<html><body>
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
||||||
weblog/index.erb:
|
weblog/index.html.erb:
|
||||||
<% for post in @posts %>
|
<% for post in @posts %>
|
||||||
<p><%= link_to(post.title, :action => "display", :id => post.id %></p>
|
<p><%= link_to(post.title, :action => "show", :id => post.id) %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
weblog/display.erb:
|
weblog/show.html.erb:
|
||||||
<p>
|
<p>
|
||||||
<b><%= post.title %></b><br/>
|
<b><%= @post.title %></b><br/>
|
||||||
<b><%= post.content %></b>
|
<b><%= @post.content %></b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
weblog/new.erb:
|
weblog/new.html.erb:
|
||||||
<%= form "post" %>
|
<%= form "post" %>
|
||||||
|
|
||||||
This simple setup will list all the posts in the system on the index page,
|
This simple setup will list all the posts in the system on the index page,
|
||||||
which is called by accessing /weblog/. It uses the form builder for the Active
|
which is called by accessing /weblog/. It uses the form builder for the Active
|
||||||
Record model to make the new screen, which in turn hands everything over to
|
Record model to make the new screen, which in turn hands everything over to
|
||||||
the create action (that's the default target for the form builder when given a
|
the create action (that's the default target for the form builder when given a
|
||||||
new model). After creating the post, it'll redirect to the display page using
|
new model). After creating the post, it'll redirect to the show page using
|
||||||
an URL such as /weblog/display/5 (where 5 is the id of the post).
|
an URL such as /weblog/5 (where 5 is the id of the post).
|
||||||
|
|
||||||
|
|
||||||
== Examples
|
|
||||||
|
|
||||||
Action Pack ships with three examples that all demonstrate an increasingly
|
|
||||||
detailed view of the possibilities. First is blog_controller that is just a
|
|
||||||
single file for the whole MVC (but still split into separate parts). Second is
|
|
||||||
the debate_controller that uses separate template files and multiple screens.
|
|
||||||
Third is the address_book_controller that uses the layout feature to separate
|
|
||||||
template casing from content.
|
|
||||||
|
|
||||||
Please note that you might need to change the "shebang" line to
|
|
||||||
#!/usr/local/env ruby, if your Ruby is not placed in /usr/local/bin/ruby
|
|
||||||
|
|
||||||
Also note that these examples are all for demonstrating using Action Pack on
|
|
||||||
its own. Not for when it's used inside of Rails.
|
|
||||||
|
|
||||||
== Download
|
== Download
|
||||||
|
|
||||||
The latest version of Action Pack can be found at
|
The latest version of Action Pack can be found at
|
||||||
|
|
|
||||||
8
vendor/rails/actionpack/Rakefile
vendored
8
vendor/rails/actionpack/Rakefile
vendored
|
|
@ -4,7 +4,6 @@ require 'rake/testtask'
|
||||||
require 'rake/rdoctask'
|
require 'rake/rdoctask'
|
||||||
require 'rake/packagetask'
|
require 'rake/packagetask'
|
||||||
require 'rake/gempackagetask'
|
require 'rake/gempackagetask'
|
||||||
require 'rake/contrib/sshpublisher'
|
|
||||||
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
|
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
|
||||||
|
|
||||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||||
|
|
@ -30,7 +29,7 @@ Rake::TestTask.new(:test_action_pack) do |t|
|
||||||
|
|
||||||
# make sure we include the tests in alphabetical order 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
|
# this will not happen automatically and the tests (as a whole) will error
|
||||||
t.test_files = Dir.glob( "test/[cft]*/**/*_test.rb" ).sort
|
t.test_files = Dir.glob( "test/[cftv]*/**/*_test.rb" ).sort
|
||||||
|
|
||||||
t.verbose = true
|
t.verbose = true
|
||||||
#t.warning = true
|
#t.warning = true
|
||||||
|
|
@ -80,7 +79,8 @@ spec = Gem::Specification.new do |s|
|
||||||
s.has_rdoc = true
|
s.has_rdoc = true
|
||||||
s.requirements << 'none'
|
s.requirements << 'none'
|
||||||
|
|
||||||
s.add_dependency('activesupport', '= 2.2.2' + PKG_BUILD)
|
s.add_dependency('activesupport', '= 2.3.5' + PKG_BUILD)
|
||||||
|
s.add_dependency('rack', '~> 1.0.0')
|
||||||
|
|
||||||
s.require_path = 'lib'
|
s.require_path = 'lib'
|
||||||
s.autorequire = 'action_controller'
|
s.autorequire = 'action_controller'
|
||||||
|
|
@ -136,12 +136,14 @@ task :update_js => [ :update_scriptaculous ]
|
||||||
|
|
||||||
desc "Publish the API documentation"
|
desc "Publish the API documentation"
|
||||||
task :pgem => [:package] do
|
task :pgem => [:package] do
|
||||||
|
require 'rake/contrib/sshpublisher'
|
||||||
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||||
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
|
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Publish the API documentation"
|
desc "Publish the API documentation"
|
||||||
task :pdoc => [:rdoc] do
|
task :pdoc => [:rdoc] do
|
||||||
|
require 'rake/contrib/sshpublisher'
|
||||||
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload
|
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
122
vendor/rails/actionpack/lib/action_controller.rb
vendored
122
vendor/rails/actionpack/lib/action_controller.rb
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
#--
|
#--
|
||||||
# Copyright (c) 2004-2008 David Heinemeier Hansson
|
# Copyright (c) 2004-2009 David Heinemeier Hansson
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
# Permission is hereby granted, free of charge, to any person obtaining
|
||||||
# a copy of this software and associated documentation files (the
|
# a copy of this software and associated documentation files (the
|
||||||
|
|
@ -31,49 +31,83 @@ rescue LoadError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
$:.unshift "#{File.dirname(__FILE__)}/action_controller/vendor/html-scanner"
|
gem 'rack', '~> 1.0.1'
|
||||||
|
require 'rack'
|
||||||
|
require 'action_controller/cgi_ext'
|
||||||
|
|
||||||
require 'action_controller/base'
|
module ActionController
|
||||||
require 'action_controller/request'
|
# TODO: Review explicit to see if they will automatically be handled by
|
||||||
require 'action_controller/rescue'
|
# the initilizer if they are really needed.
|
||||||
require 'action_controller/benchmarking'
|
def self.load_all!
|
||||||
require 'action_controller/flash'
|
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
|
||||||
require 'action_controller/filters'
|
end
|
||||||
require 'action_controller/layout'
|
|
||||||
require 'action_controller/mime_responds'
|
autoload :Base, 'action_controller/base'
|
||||||
require 'action_controller/helpers'
|
autoload :Benchmarking, 'action_controller/benchmarking'
|
||||||
require 'action_controller/cookies'
|
autoload :Caching, 'action_controller/caching'
|
||||||
require 'action_controller/cgi_process'
|
autoload :Cookies, 'action_controller/cookies'
|
||||||
require 'action_controller/caching'
|
autoload :Dispatcher, 'action_controller/dispatcher'
|
||||||
require 'action_controller/verification'
|
autoload :Failsafe, 'action_controller/failsafe'
|
||||||
require 'action_controller/streaming'
|
autoload :Filters, 'action_controller/filters'
|
||||||
require 'action_controller/session_management'
|
autoload :Flash, 'action_controller/flash'
|
||||||
require 'action_controller/http_authentication'
|
autoload :Helpers, 'action_controller/helpers'
|
||||||
require 'action_controller/components'
|
autoload :HttpAuthentication, 'action_controller/http_authentication'
|
||||||
require 'action_controller/rack_process'
|
autoload :Integration, 'action_controller/integration'
|
||||||
require 'action_controller/record_identifier'
|
autoload :IntegrationTest, 'action_controller/integration'
|
||||||
require 'action_controller/request_forgery_protection'
|
autoload :Layout, 'action_controller/layout'
|
||||||
require 'action_controller/headers'
|
autoload :MiddlewareStack, 'action_controller/middleware_stack'
|
||||||
require 'action_controller/translation'
|
autoload :MimeResponds, 'action_controller/mime_responds'
|
||||||
|
autoload :ParamsParser, 'action_controller/params_parser'
|
||||||
|
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
|
||||||
|
autoload :RecordIdentifier, 'action_controller/record_identifier'
|
||||||
|
autoload :Reloader, 'action_controller/reloader'
|
||||||
|
autoload :Request, 'action_controller/request'
|
||||||
|
autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection'
|
||||||
|
autoload :Rescue, 'action_controller/rescue'
|
||||||
|
autoload :Resources, 'action_controller/resources'
|
||||||
|
autoload :Response, 'action_controller/response'
|
||||||
|
autoload :RewindableInput, 'action_controller/rewindable_input'
|
||||||
|
autoload :Routing, 'action_controller/routing'
|
||||||
|
autoload :SessionManagement, 'action_controller/session_management'
|
||||||
|
autoload :StatusCodes, 'action_controller/status_codes'
|
||||||
|
autoload :Streaming, 'action_controller/streaming'
|
||||||
|
autoload :StringCoercion, 'action_controller/string_coercion'
|
||||||
|
autoload :TestCase, 'action_controller/test_case'
|
||||||
|
autoload :TestProcess, 'action_controller/test_process'
|
||||||
|
autoload :Translation, 'action_controller/translation'
|
||||||
|
autoload :UploadedFile, 'action_controller/uploaded_file'
|
||||||
|
autoload :UploadedStringIO, 'action_controller/uploaded_file'
|
||||||
|
autoload :UploadedTempfile, 'action_controller/uploaded_file'
|
||||||
|
autoload :UrlRewriter, 'action_controller/url_rewriter'
|
||||||
|
autoload :UrlWriter, 'action_controller/url_rewriter'
|
||||||
|
autoload :Verification, 'action_controller/verification'
|
||||||
|
|
||||||
|
module Assertions
|
||||||
|
autoload :DomAssertions, 'action_controller/assertions/dom_assertions'
|
||||||
|
autoload :ModelAssertions, 'action_controller/assertions/model_assertions'
|
||||||
|
autoload :ResponseAssertions, 'action_controller/assertions/response_assertions'
|
||||||
|
autoload :RoutingAssertions, 'action_controller/assertions/routing_assertions'
|
||||||
|
autoload :SelectorAssertions, 'action_controller/assertions/selector_assertions'
|
||||||
|
autoload :TagAssertions, 'action_controller/assertions/tag_assertions'
|
||||||
|
end
|
||||||
|
|
||||||
|
module Http
|
||||||
|
autoload :Headers, 'action_controller/headers'
|
||||||
|
end
|
||||||
|
|
||||||
|
module Session
|
||||||
|
autoload :AbstractStore, 'action_controller/session/abstract_store'
|
||||||
|
autoload :CookieStore, 'action_controller/session/cookie_store'
|
||||||
|
autoload :MemCacheStore, 'action_controller/session/mem_cache_store'
|
||||||
|
end
|
||||||
|
|
||||||
|
# DEPRECATE: Remove CGI support
|
||||||
|
autoload :CgiRequest, 'action_controller/cgi_process'
|
||||||
|
autoload :CGIHandler, 'action_controller/cgi_process'
|
||||||
|
end
|
||||||
|
|
||||||
|
autoload :Mime, 'action_controller/mime_type'
|
||||||
|
|
||||||
|
autoload :HTML, 'action_controller/vendor/html-scanner'
|
||||||
|
|
||||||
require 'action_view'
|
require 'action_view'
|
||||||
|
|
||||||
ActionController::Base.class_eval do
|
|
||||||
include ActionController::Flash
|
|
||||||
include ActionController::Filters
|
|
||||||
include ActionController::Layout
|
|
||||||
include ActionController::Benchmarking
|
|
||||||
include ActionController::Rescue
|
|
||||||
include ActionController::MimeResponds
|
|
||||||
include ActionController::Helpers
|
|
||||||
include ActionController::Cookies
|
|
||||||
include ActionController::Caching
|
|
||||||
include ActionController::Verification
|
|
||||||
include ActionController::Streaming
|
|
||||||
include ActionController::SessionManagement
|
|
||||||
include ActionController::HttpAuthentication::Basic::ControllerMethods
|
|
||||||
include ActionController::Components
|
|
||||||
include ActionController::RecordIdentifier
|
|
||||||
include ActionController::RequestForgeryProtection
|
|
||||||
include ActionController::Translation
|
|
||||||
end
|
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
require 'test/unit/assertions'
|
|
||||||
|
|
||||||
module ActionController #:nodoc:
|
|
||||||
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
|
|
||||||
# can be used against. These collections are:
|
|
||||||
#
|
|
||||||
# * assigns: Instance variables assigned in the action that are available for the view.
|
|
||||||
# * session: Objects being saved in the session.
|
|
||||||
# * flash: The flash objects currently in the session.
|
|
||||||
# * cookies: Cookies being sent to the user on this request.
|
|
||||||
#
|
|
||||||
# These collections can be used just like any other hash:
|
|
||||||
#
|
|
||||||
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
|
|
||||||
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
|
|
||||||
# assert flash.empty? # makes sure that there's nothing in the flash
|
|
||||||
#
|
|
||||||
# For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
|
|
||||||
# appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
|
|
||||||
# So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
|
|
||||||
#
|
|
||||||
# On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
|
|
||||||
#
|
|
||||||
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
|
|
||||||
# action call which can then be asserted against.
|
|
||||||
#
|
|
||||||
# == Manipulating the request collections
|
|
||||||
#
|
|
||||||
# The collections described above link to the response, so you can test if what the actions were expected to do happened. But
|
|
||||||
# sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
|
|
||||||
# and cookies, though. For sessions, you just do:
|
|
||||||
#
|
|
||||||
# @request.session[:key] = "value"
|
|
||||||
#
|
|
||||||
# For cookies, you need to manually create the cookie, like this:
|
|
||||||
#
|
|
||||||
# @request.cookies["key"] = CGI::Cookie.new("key", "value")
|
|
||||||
#
|
|
||||||
# == Testing named routes
|
|
||||||
#
|
|
||||||
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
|
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# assert_redirected_to page_url(:title => 'foo')
|
|
||||||
module Assertions
|
|
||||||
def self.included(klass)
|
|
||||||
%w(response selector tag dom routing model).each do |kind|
|
|
||||||
require "action_controller/assertions/#{kind}_assertions"
|
|
||||||
klass.module_eval { include const_get("#{kind.camelize}Assertions") }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def clean_backtrace(&block)
|
|
||||||
yield
|
|
||||||
rescue Test::Unit::AssertionFailedError => error
|
|
||||||
framework_path = Regexp.new(File.expand_path("#{File.dirname(__FILE__)}/assertions"))
|
|
||||||
error.backtrace.reject! { |line| File.expand_path(line) =~ framework_path }
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module Test #:nodoc:
|
|
||||||
module Unit #:nodoc:
|
|
||||||
class TestCase #:nodoc:
|
|
||||||
include ActionController::Assertions
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,6 +1,18 @@
|
||||||
module ActionController
|
module ActionController
|
||||||
module Assertions
|
module Assertions
|
||||||
module DomAssertions
|
module DomAssertions
|
||||||
|
def self.strip_whitespace!(nodes)
|
||||||
|
nodes.reject! do |node|
|
||||||
|
if node.is_a?(HTML::Text)
|
||||||
|
node.content.strip!
|
||||||
|
node.content.empty?
|
||||||
|
else
|
||||||
|
strip_whitespace! node.children
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
|
# Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
|
||||||
#
|
#
|
||||||
# ==== Examples
|
# ==== Examples
|
||||||
|
|
@ -12,13 +24,15 @@ module ActionController
|
||||||
clean_backtrace do
|
clean_backtrace do
|
||||||
expected_dom = HTML::Document.new(expected).root
|
expected_dom = HTML::Document.new(expected).root
|
||||||
actual_dom = HTML::Document.new(actual).root
|
actual_dom = HTML::Document.new(actual).root
|
||||||
full_message = build_message(message, "<?> expected to be == to\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
DomAssertions.strip_whitespace!(expected_dom.children)
|
||||||
|
DomAssertions.strip_whitespace!(actual_dom.children)
|
||||||
|
|
||||||
|
full_message = build_message(message, "<?> expected but was\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
||||||
assert_block(full_message) { expected_dom == actual_dom }
|
assert_block(full_message) { expected_dom == actual_dom }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# The negated form of +assert_dom_equivalent+.
|
# The negated form of +assert_dom_equal+.
|
||||||
#
|
#
|
||||||
# ==== Examples
|
# ==== Examples
|
||||||
#
|
#
|
||||||
|
|
@ -29,8 +43,10 @@ module ActionController
|
||||||
clean_backtrace do
|
clean_backtrace do
|
||||||
expected_dom = HTML::Document.new(expected).root
|
expected_dom = HTML::Document.new(expected).root
|
||||||
actual_dom = HTML::Document.new(actual).root
|
actual_dom = HTML::Document.new(actual).root
|
||||||
full_message = build_message(message, "<?> expected to be != to\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
DomAssertions.strip_whitespace!(expected_dom.children)
|
||||||
|
DomAssertions.strip_whitespace!(actual_dom.children)
|
||||||
|
|
||||||
|
full_message = build_message(message, "<?> expected to be != to\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
||||||
assert_block(full_message) { expected_dom != actual_dom }
|
assert_block(full_message) { expected_dom != actual_dom }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ module ActionController
|
||||||
# assert_valid(model)
|
# assert_valid(model)
|
||||||
#
|
#
|
||||||
def assert_valid(record)
|
def assert_valid(record)
|
||||||
|
::ActiveSupport::Deprecation.warn("assert_valid is deprecated. Use assert record.valid? instead", caller)
|
||||||
clean_backtrace do
|
clean_backtrace do
|
||||||
assert record.valid?, record.errors.full_messages.join("\n")
|
assert record.valid?, record.errors.full_messages.join("\n")
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
require 'rexml/document'
|
|
||||||
require 'html/document'
|
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module Assertions
|
module Assertions
|
||||||
# A small suite of assertions that test responses from Rails applications.
|
# A small suite of assertions that test responses from Rails applications.
|
||||||
|
|
@ -66,7 +63,12 @@ module ActionController
|
||||||
|
|
||||||
# Support partial arguments for hash redirections
|
# Support partial arguments for hash redirections
|
||||||
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
|
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
|
||||||
return true if options.all? {|(key, value)| @response.redirected_to[key] == value}
|
if options.all? {|(key, value)| @response.redirected_to[key] == value}
|
||||||
|
callstack = caller.dup
|
||||||
|
callstack.slice!(0, 2)
|
||||||
|
::ActiveSupport::Deprecation.warn("Using assert_redirected_to with partial hash arguments is deprecated. Specify the full set arguments instead", callstack)
|
||||||
|
return true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
|
redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
|
||||||
|
|
@ -78,29 +80,64 @@ module ActionController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Asserts that the request was rendered with the appropriate template file.
|
# Asserts that the request was rendered with the appropriate template file or partials
|
||||||
#
|
#
|
||||||
# ==== Examples
|
# ==== Examples
|
||||||
#
|
#
|
||||||
# # assert that the "new" view template was rendered
|
# # assert that the "new" view template was rendered
|
||||||
# assert_template "new"
|
# assert_template "new"
|
||||||
#
|
#
|
||||||
def assert_template(expected = nil, message=nil)
|
# # assert that the "new" view template was rendered with Symbol
|
||||||
|
# assert_template :new
|
||||||
|
#
|
||||||
|
# # assert that the "_customer" partial was rendered twice
|
||||||
|
# assert_template :partial => '_customer', :count => 2
|
||||||
|
#
|
||||||
|
# # assert that no partials were rendered
|
||||||
|
# assert_template :partial => false
|
||||||
|
#
|
||||||
|
def assert_template(options = {}, message = nil)
|
||||||
clean_backtrace do
|
clean_backtrace do
|
||||||
rendered = @response.rendered_template.to_s
|
case options
|
||||||
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
|
when NilClass, String, Symbol
|
||||||
|
rendered = @response.rendered[:template].to_s
|
||||||
|
msg = build_message(message,
|
||||||
|
"expecting <?> but rendering with <?>",
|
||||||
|
options, rendered)
|
||||||
assert_block(msg) do
|
assert_block(msg) do
|
||||||
if expected.nil?
|
if options.nil?
|
||||||
@response.rendered_template.blank?
|
@response.rendered[:template].blank?
|
||||||
else
|
else
|
||||||
rendered.to_s.match(expected)
|
rendered.to_s.match(options.to_s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
when Hash
|
||||||
|
if expected_partial = options[:partial]
|
||||||
|
partials = @response.rendered[:partials]
|
||||||
|
if expected_count = options[:count]
|
||||||
|
found = partials.detect { |p, _| p.to_s.match(expected_partial) }
|
||||||
|
actual_count = found.nil? ? 0 : found.second
|
||||||
|
msg = build_message(message,
|
||||||
|
"expecting ? to be rendered ? time(s) but rendered ? time(s)",
|
||||||
|
expected_partial, expected_count, actual_count)
|
||||||
|
assert(actual_count == expected_count.to_i, msg)
|
||||||
|
else
|
||||||
|
msg = build_message(message,
|
||||||
|
"expecting partial <?> but action rendered <?>",
|
||||||
|
options[:partial], partials.keys)
|
||||||
|
assert(partials.keys.any? { |p| p.to_s.match(expected_partial) }, msg)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
assert @response.rendered[:partials].empty?,
|
||||||
|
"Expected no partials to be rendered"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise ArgumentError
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Proxy to to_param if the object will respond to it.
|
# Proxy to to_param if the object will respond to it.
|
||||||
def parameterize(value)
|
def parameterize(value)
|
||||||
value.respond_to?(:to_param) ? value.to_param : value
|
value.respond_to?(:to_param) ? value.to_param : value
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ module ActionController
|
||||||
path = "/#{path}" unless path.first == '/'
|
path = "/#{path}" unless path.first == '/'
|
||||||
|
|
||||||
# Assume given controller
|
# Assume given controller
|
||||||
request = ActionController::TestRequest.new({}, {}, nil)
|
request = ActionController::TestRequest.new
|
||||||
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
|
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
|
||||||
request.path = path
|
request.path = path
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,6 @@
|
||||||
# Under MIT and/or CC By license.
|
# Under MIT and/or CC By license.
|
||||||
#++
|
#++
|
||||||
|
|
||||||
require 'rexml/document'
|
|
||||||
require 'html/document'
|
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module Assertions
|
module Assertions
|
||||||
unless const_defined?(:NO_STRIP)
|
unless const_defined?(:NO_STRIP)
|
||||||
|
|
@ -27,6 +24,12 @@ module ActionController
|
||||||
#
|
#
|
||||||
# Also see HTML::Selector to learn how to use selectors.
|
# Also see HTML::Selector to learn how to use selectors.
|
||||||
module SelectorAssertions
|
module SelectorAssertions
|
||||||
|
|
||||||
|
def initialize(*args)
|
||||||
|
super
|
||||||
|
@selected = nil
|
||||||
|
end
|
||||||
|
|
||||||
# :call-seq:
|
# :call-seq:
|
||||||
# css_select(selector) => array
|
# css_select(selector) => array
|
||||||
# css_select(element, selector) => array
|
# css_select(element, selector) => array
|
||||||
|
|
@ -112,20 +115,27 @@ module ActionController
|
||||||
# starting from (and including) that element and all its children in
|
# starting from (and including) that element and all its children in
|
||||||
# depth-first order.
|
# depth-first order.
|
||||||
#
|
#
|
||||||
# If no element if specified, calling +assert_select+ will select from the
|
# If no element if specified, calling +assert_select+ selects from the
|
||||||
# response HTML. Calling #assert_select inside an +assert_select+ block will
|
# response HTML unless +assert_select+ is called from within an +assert_select+ block.
|
||||||
# run the assertion for each element selected by the enclosing assertion.
|
#
|
||||||
|
# When called with a block +assert_select+ passes an array of selected elements
|
||||||
|
# to the block. Calling +assert_select+ from the block, with no element specified,
|
||||||
|
# runs the assertion on the complete set of elements selected by the enclosing assertion.
|
||||||
|
# Alternatively the array may be iterated through so that +assert_select+ can be called
|
||||||
|
# separately for each element.
|
||||||
|
#
|
||||||
#
|
#
|
||||||
# ==== Example
|
# ==== Example
|
||||||
# assert_select "ol>li" do |elements|
|
# If the response contains two ordered lists, each with four list elements then:
|
||||||
|
# assert_select "ol" do |elements|
|
||||||
# elements.each do |element|
|
# elements.each do |element|
|
||||||
# assert_select element, "li"
|
# assert_select element, "li", 4
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Or for short:
|
# will pass, as will:
|
||||||
# assert_select "ol>li" do
|
# assert_select "ol" do
|
||||||
# assert_select "li"
|
# assert_select "li", 8
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# The selector may be a CSS selector expression (String), an expression
|
# The selector may be a CSS selector expression (String), an expression
|
||||||
|
|
@ -405,6 +415,7 @@ module ActionController
|
||||||
if rjs_type
|
if rjs_type
|
||||||
if rjs_type == :insert
|
if rjs_type == :insert
|
||||||
position = args.shift
|
position = args.shift
|
||||||
|
id = args.shift
|
||||||
insertion = "insert_#{position}".to_sym
|
insertion = "insert_#{position}".to_sym
|
||||||
raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion]
|
raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion]
|
||||||
statement = "(#{RJS_STATEMENTS[insertion]})"
|
statement = "(#{RJS_STATEMENTS[insertion]})"
|
||||||
|
|
@ -590,7 +601,7 @@ module ActionController
|
||||||
def response_from_page_or_rjs()
|
def response_from_page_or_rjs()
|
||||||
content_type = @response.content_type
|
content_type = @response.content_type
|
||||||
|
|
||||||
if content_type && content_type =~ /text\/javascript/
|
if content_type && Mime::JS =~ content_type
|
||||||
body = @response.body.dup
|
body = @response.body.dup
|
||||||
root = HTML::Node.new(nil)
|
root = HTML::Node.new(nil)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
require 'rexml/document'
|
|
||||||
require 'html/document'
|
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module Assertions
|
module Assertions
|
||||||
# Pair of assertions to testing elements in the HTML output of the response.
|
# Pair of assertions to testing elements in the HTML output of the response.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,3 @@
|
||||||
require 'action_controller/mime_type'
|
|
||||||
require 'action_controller/request'
|
|
||||||
require 'action_controller/response'
|
|
||||||
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'
|
require 'set'
|
||||||
|
|
||||||
module ActionController #:nodoc:
|
module ActionController #:nodoc:
|
||||||
|
|
@ -31,7 +22,7 @@ module ActionController #:nodoc:
|
||||||
attr_reader :allowed_methods
|
attr_reader :allowed_methods
|
||||||
|
|
||||||
def initialize(*allowed_methods)
|
def initialize(*allowed_methods)
|
||||||
super("Only #{allowed_methods.to_sentence} requests are allowed.")
|
super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
|
||||||
@allowed_methods = allowed_methods
|
@allowed_methods = allowed_methods
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -173,8 +164,8 @@ module ActionController #:nodoc:
|
||||||
#
|
#
|
||||||
# Other options for session storage are:
|
# Other options for session storage are:
|
||||||
#
|
#
|
||||||
# * ActiveRecordStore - Sessions are stored in your database, which works better than PStore with multiple app servers and,
|
# * ActiveRecord::SessionStore - 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
|
# unlike CookieStore, hides your session contents from the user. To use ActiveRecord::SessionStore, set
|
||||||
#
|
#
|
||||||
# config.action_controller.session_store = :active_record_store
|
# config.action_controller.session_store = :active_record_store
|
||||||
#
|
#
|
||||||
|
|
@ -263,7 +254,7 @@ module ActionController #:nodoc:
|
||||||
cattr_reader :protected_instance_variables
|
cattr_reader :protected_instance_variables
|
||||||
# Controller specific instance variables which will not be accessible inside views.
|
# Controller specific instance variables which will not be accessible inside views.
|
||||||
@@protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller
|
@@protected_instance_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
|
@action_name @before_filter_chain_aborted @action_cache_path @_session @_headers @_params
|
||||||
@_flash @_response)
|
@_flash @_response)
|
||||||
|
|
||||||
# Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
|
# Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
|
||||||
|
|
@ -310,10 +301,7 @@ module ActionController #:nodoc:
|
||||||
# A YAML parser is also available and can be turned on with:
|
# A YAML parser is also available and can be turned on with:
|
||||||
#
|
#
|
||||||
# ActionController::Base.param_parsers[Mime::YAML] = :yaml
|
# ActionController::Base.param_parsers[Mime::YAML] = :yaml
|
||||||
@@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
|
@@param_parsers = {}
|
||||||
Mime::URL_ENCODED_FORM => :url_encoded_form,
|
|
||||||
Mime::XML => :xml_simple,
|
|
||||||
Mime::JSON => :json }
|
|
||||||
cattr_accessor :param_parsers
|
cattr_accessor :param_parsers
|
||||||
|
|
||||||
# Controls the default charset for all renders.
|
# Controls the default charset for all renders.
|
||||||
|
|
@ -336,6 +324,10 @@ module ActionController #:nodoc:
|
||||||
# sets it to <tt>:authenticity_token</tt> by default.
|
# sets it to <tt>:authenticity_token</tt> by default.
|
||||||
cattr_accessor :request_forgery_protection_token
|
cattr_accessor :request_forgery_protection_token
|
||||||
|
|
||||||
|
# Controls the IP Spoofing check when determining the remote IP.
|
||||||
|
@@ip_spoofing_check = true
|
||||||
|
cattr_accessor :ip_spoofing_check
|
||||||
|
|
||||||
# Indicates whether or not optimise the generated named
|
# Indicates whether or not optimise the generated named
|
||||||
# route helper methods
|
# route helper methods
|
||||||
cattr_accessor :optimise_named_routes
|
cattr_accessor :optimise_named_routes
|
||||||
|
|
@ -387,6 +379,13 @@ module ActionController #:nodoc:
|
||||||
attr_accessor :action_name
|
attr_accessor :action_name
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
def call(env)
|
||||||
|
# HACK: For global rescue to have access to the original request and response
|
||||||
|
request = env["action_controller.rescue.request"] ||= Request.new(env)
|
||||||
|
response = env["action_controller.rescue.response"] ||= Response.new
|
||||||
|
process(request, response)
|
||||||
|
end
|
||||||
|
|
||||||
# Factory for the standard create, process loop where the controller is discarded after processing.
|
# Factory for the standard create, process loop where the controller is discarded after processing.
|
||||||
def process(request, response) #:nodoc:
|
def process(request, response) #:nodoc:
|
||||||
new.process(request, response)
|
new.process(request, response)
|
||||||
|
|
@ -492,9 +491,18 @@ module ActionController #:nodoc:
|
||||||
filtered_parameters[key] = '[FILTERED]'
|
filtered_parameters[key] = '[FILTERED]'
|
||||||
elsif value.is_a?(Hash)
|
elsif value.is_a?(Hash)
|
||||||
filtered_parameters[key] = filter_parameters(value)
|
filtered_parameters[key] = filter_parameters(value)
|
||||||
|
elsif value.is_a?(Array)
|
||||||
|
filtered_parameters[key] = value.collect do |item|
|
||||||
|
case item
|
||||||
|
when Hash, Array
|
||||||
|
filter_parameters(item)
|
||||||
|
else
|
||||||
|
item
|
||||||
|
end
|
||||||
|
end
|
||||||
elsif block_given?
|
elsif block_given?
|
||||||
key = key.dup
|
key = key.dup
|
||||||
value = value.dup if value
|
value = value.dup if value.duplicable?
|
||||||
yield key, value
|
yield key, value
|
||||||
filtered_parameters[key] = value
|
filtered_parameters[key] = value
|
||||||
else
|
else
|
||||||
|
|
@ -507,7 +515,7 @@ module ActionController #:nodoc:
|
||||||
protected :filter_parameters
|
protected :filter_parameters
|
||||||
end
|
end
|
||||||
|
|
||||||
delegate :exempt_from_layout, :to => 'ActionView::Base'
|
delegate :exempt_from_layout, :to => 'ActionView::Template'
|
||||||
end
|
end
|
||||||
|
|
||||||
public
|
public
|
||||||
|
|
@ -529,7 +537,7 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_response
|
def send_response
|
||||||
response.prepare! unless component_request?
|
response.prepare!
|
||||||
response
|
response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -645,7 +653,7 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def session_enabled?
|
def session_enabled?
|
||||||
request.session_options && request.session_options[:disabled] != false
|
ActiveSupport::Deprecation.warn("Sessions are now lazy loaded. So if you don't access them, consider them disabled.", caller)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.view_paths = []
|
self.view_paths = []
|
||||||
|
|
@ -785,9 +793,36 @@ module ActionController #:nodoc:
|
||||||
# # placed in "app/views/layouts/special.r(html|xml)"
|
# # placed in "app/views/layouts/special.r(html|xml)"
|
||||||
# render :text => "Hi there!", :layout => "special"
|
# render :text => "Hi there!", :layout => "special"
|
||||||
#
|
#
|
||||||
# The <tt>:text</tt> option can also accept a Proc object, which can be used to manually control the page generation. This should
|
# === Streaming data and/or controlling the page generation
|
||||||
# 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.
|
# The <tt>:text</tt> option can also accept a Proc object, which can be used to:
|
||||||
|
#
|
||||||
|
# 1. stream on-the-fly generated data to the browser. Note that you should
|
||||||
|
# use the methods provided by ActionController::Steaming instead if you
|
||||||
|
# want to stream a buffer or a file.
|
||||||
|
# 2. 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.
|
||||||
|
#
|
||||||
|
# Two arguments are passed to the proc, a <tt>response</tt> object and an
|
||||||
|
# <tt>output</tt> object. The response object is equivalent to the return
|
||||||
|
# value of the ActionController::Base#response method, and can be used to
|
||||||
|
# control various things in the HTTP response, such as setting the
|
||||||
|
# Content-Type header. The output object is an writable <tt>IO</tt>-like
|
||||||
|
# object, so one can call <tt>write</tt> and <tt>flush</tt> on it.
|
||||||
|
#
|
||||||
|
# The following example demonstrates how one can stream a large amount of
|
||||||
|
# on-the-fly generated data to the browser:
|
||||||
|
#
|
||||||
|
# # Streams about 180 MB of generated data to the browser.
|
||||||
|
# render :text => proc { |response, output|
|
||||||
|
# 10_000_000.times do |i|
|
||||||
|
# output.write("This is line #{i}\n")
|
||||||
|
# end
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# Another example:
|
||||||
#
|
#
|
||||||
# # Renders "Hello from code!"
|
# # Renders "Hello from code!"
|
||||||
# render :text => proc { |response, output| output.write("Hello from code!") }
|
# render :text => proc { |response, output| output.write("Hello from code!") }
|
||||||
|
|
@ -864,20 +899,31 @@ module ActionController #:nodoc:
|
||||||
def render(options = nil, extra_options = {}, &block) #:doc:
|
def render(options = nil, extra_options = {}, &block) #:doc:
|
||||||
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
|
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
|
||||||
|
|
||||||
|
validate_render_arguments(options, extra_options, block_given?)
|
||||||
|
|
||||||
if options.nil?
|
if options.nil?
|
||||||
return render(:file => default_template_name, :layout => true)
|
options = { :template => default_template, :layout => true }
|
||||||
elsif !extra_options.is_a?(Hash)
|
elsif options == :update
|
||||||
raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
|
|
||||||
else
|
|
||||||
if options == :update
|
|
||||||
options = extra_options.merge({ :update => true })
|
options = extra_options.merge({ :update => true })
|
||||||
elsif !options.is_a?(Hash)
|
elsif options.is_a?(String) || options.is_a?(Symbol)
|
||||||
raise RenderError, "You called render with invalid options : #{options.inspect}"
|
case options.to_s.index('/')
|
||||||
end
|
when 0
|
||||||
|
extra_options[:file] = options
|
||||||
|
when nil
|
||||||
|
extra_options[:action] = options
|
||||||
|
else
|
||||||
|
extra_options[:template] = options
|
||||||
end
|
end
|
||||||
|
|
||||||
response.layout = layout = pick_layout(options)
|
options = extra_options
|
||||||
logger.info("Rendering template within #{layout}") if logger && layout
|
elsif !options.is_a?(Hash)
|
||||||
|
extra_options[:partial] = options
|
||||||
|
options = extra_options
|
||||||
|
end
|
||||||
|
|
||||||
|
layout = pick_layout(options)
|
||||||
|
response.layout = layout.path_without_format_and_extension if layout
|
||||||
|
logger.info("Rendering template within #{layout.path_without_format_and_extension}") if logger && layout
|
||||||
|
|
||||||
if content_type = options[:content_type]
|
if content_type = options[:content_type]
|
||||||
response.content_type = content_type.to_s
|
response.content_type = content_type.to_s
|
||||||
|
|
@ -902,7 +948,7 @@ module ActionController #:nodoc:
|
||||||
render_for_text(@template.render(options.merge(:layout => layout)), options[:status])
|
render_for_text(@template.render(options.merge(:layout => layout)), options[:status])
|
||||||
|
|
||||||
elsif action_name = options[:action]
|
elsif action_name = options[:action]
|
||||||
render_for_file(default_template_name(action_name.to_s), options[:status], layout)
|
render_for_file(default_template(action_name.to_s), options[:status], layout)
|
||||||
|
|
||||||
elsif xml = options[:xml]
|
elsif xml = options[:xml]
|
||||||
response.content_type ||= Mime::XML
|
response.content_type ||= Mime::XML
|
||||||
|
|
@ -912,8 +958,9 @@ module ActionController #:nodoc:
|
||||||
response.content_type ||= Mime::JS
|
response.content_type ||= Mime::JS
|
||||||
render_for_text(js, options[:status])
|
render_for_text(js, options[:status])
|
||||||
|
|
||||||
elsif json = options[:json]
|
elsif options.include?(:json)
|
||||||
json = json.to_json unless json.is_a?(String)
|
json = options[:json]
|
||||||
|
json = ActiveSupport::JSON.encode(json) unless json.is_a?(String)
|
||||||
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
|
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
|
||||||
response.content_type ||= Mime::JSON
|
response.content_type ||= Mime::JSON
|
||||||
render_for_text(json, options[:status])
|
render_for_text(json, options[:status])
|
||||||
|
|
@ -937,7 +984,7 @@ module ActionController #:nodoc:
|
||||||
render_for_text(nil, options[:status])
|
render_for_text(nil, options[:status])
|
||||||
|
|
||||||
else
|
else
|
||||||
render_for_file(default_template_name, options[:status], layout)
|
render_for_file(default_template, options[:status], layout)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -994,7 +1041,7 @@ module ActionController #:nodoc:
|
||||||
@performed_redirect = false
|
@performed_redirect = false
|
||||||
response.redirected_to = nil
|
response.redirected_to = nil
|
||||||
response.redirected_to_method_params = nil
|
response.redirected_to_method_params = nil
|
||||||
response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE
|
response.status = DEFAULT_RENDER_STATUS_CODE
|
||||||
response.headers.delete('Location')
|
response.headers.delete('Location')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1065,7 +1112,6 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
response.redirected_to = options
|
response.redirected_to = options
|
||||||
logger.info("Redirected to #{options}") if logger && logger.info?
|
|
||||||
|
|
||||||
case options
|
case options
|
||||||
# The scheme name consist of a letter followed by any combination of
|
# The scheme name consist of a letter followed by any combination of
|
||||||
|
|
@ -1088,6 +1134,7 @@ module ActionController #:nodoc:
|
||||||
|
|
||||||
def redirect_to_full_url(url, status)
|
def redirect_to_full_url(url, status)
|
||||||
raise DoubleRenderError if performed?
|
raise DoubleRenderError if performed?
|
||||||
|
logger.info("Redirected to #{url}") if logger && logger.info?
|
||||||
response.redirect(url, interpret_status(status))
|
response.redirect(url, interpret_status(status))
|
||||||
@performed_redirect = true
|
@performed_redirect = true
|
||||||
end
|
end
|
||||||
|
|
@ -1097,6 +1144,11 @@ module ActionController #:nodoc:
|
||||||
# request is considered stale and should be generated from scratch. Otherwise,
|
# request is considered stale and should be generated from scratch. Otherwise,
|
||||||
# it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent.
|
# it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent.
|
||||||
#
|
#
|
||||||
|
# Parameters:
|
||||||
|
# * <tt>:etag</tt>
|
||||||
|
# * <tt>:last_modified</tt>
|
||||||
|
# * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
|
||||||
|
#
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
|
|
@ -1117,21 +1169,35 @@ module ActionController #:nodoc:
|
||||||
# Sets the etag, last_modified, or both on the response and renders a
|
# Sets the etag, last_modified, or both on the response and renders a
|
||||||
# "304 Not Modified" response if the request is already fresh.
|
# "304 Not Modified" response if the request is already fresh.
|
||||||
#
|
#
|
||||||
|
# Parameters:
|
||||||
|
# * <tt>:etag</tt>
|
||||||
|
# * <tt>:last_modified</tt>
|
||||||
|
# * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
|
||||||
|
#
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# @article = Article.find(params[:id])
|
# @article = Article.find(params[:id])
|
||||||
# fresh_when(:etag => @article, :last_modified => @article.created_at.utc)
|
# fresh_when(:etag => @article, :last_modified => @article.created_at.utc, :public => true)
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# This will render the show template if the request isn't sending a matching etag or
|
# This will render the show template if the request isn't sending a matching etag or
|
||||||
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
|
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
|
||||||
|
#
|
||||||
def fresh_when(options)
|
def fresh_when(options)
|
||||||
options.assert_valid_keys(:etag, :last_modified)
|
options.assert_valid_keys(:etag, :last_modified, :public)
|
||||||
|
|
||||||
response.etag = options[:etag] if options[:etag]
|
response.etag = options[:etag] if options[:etag]
|
||||||
response.last_modified = options[:last_modified] if options[:last_modified]
|
response.last_modified = options[:last_modified] if options[:last_modified]
|
||||||
|
|
||||||
|
if options[:public]
|
||||||
|
cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
|
||||||
|
cache_control.delete("private")
|
||||||
|
cache_control.delete("no-cache")
|
||||||
|
cache_control << "public"
|
||||||
|
response.headers["Cache-Control"] = cache_control.join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
if request.fresh?(response)
|
if request.fresh?(response)
|
||||||
head :not_modified
|
head :not_modified
|
||||||
end
|
end
|
||||||
|
|
@ -1142,15 +1208,26 @@ module ActionController #:nodoc:
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# expires_in 20.minutes
|
# expires_in 20.minutes
|
||||||
# expires_in 3.hours, :private => false
|
# expires_in 3.hours, :public => true
|
||||||
# expires in 3.hours, 'max-stale' => 5.hours, :private => nil, :public => true
|
# expires in 3.hours, 'max-stale' => 5.hours, :public => true
|
||||||
#
|
#
|
||||||
# This method will overwrite an existing Cache-Control header.
|
# This method will overwrite an existing Cache-Control header.
|
||||||
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
|
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
|
||||||
def expires_in(seconds, options = {}) #:doc:
|
def expires_in(seconds, options = {}) #:doc:
|
||||||
cache_options = { 'max-age' => seconds, 'private' => true }.symbolize_keys.merge!(options.symbolize_keys)
|
cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
|
||||||
cache_options.delete_if { |k,v| v.nil? or v == false }
|
|
||||||
cache_control = cache_options.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
|
cache_control << "max-age=#{seconds}"
|
||||||
|
cache_control.delete("no-cache")
|
||||||
|
if options[:public]
|
||||||
|
cache_control.delete("private")
|
||||||
|
cache_control << "public"
|
||||||
|
else
|
||||||
|
cache_control << "private"
|
||||||
|
end
|
||||||
|
|
||||||
|
# This allows for additional headers to be passed through like 'max-stale' => 5.hours
|
||||||
|
cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
|
||||||
|
|
||||||
response.headers["Cache-Control"] = cache_control.join(', ')
|
response.headers["Cache-Control"] = cache_control.join(', ')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1164,20 +1241,19 @@ module ActionController #:nodoc:
|
||||||
def reset_session #:doc:
|
def reset_session #:doc:
|
||||||
request.reset_session
|
request.reset_session
|
||||||
@_session = request.session
|
@_session = request.session
|
||||||
response.session = @_session
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def render_for_file(template_path, status = nil, layout = nil, locals = {}) #:nodoc:
|
def render_for_file(template_path, status = nil, layout = nil, locals = {}) #:nodoc:
|
||||||
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
|
path = template_path.respond_to?(:path_without_format_and_extension) ? template_path.path_without_format_and_extension : template_path
|
||||||
|
logger.info("Rendering #{path}" + (status ? " (#{status})" : '')) if logger
|
||||||
render_for_text @template.render(:file => template_path, :locals => locals, :layout => layout), status
|
render_for_text @template.render(:file => template_path, :locals => locals, :layout => layout), status
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_for_text(text = nil, status = nil, append_response = false) #:nodoc:
|
def render_for_text(text = nil, status = nil, append_response = false) #:nodoc:
|
||||||
@performed_render = true
|
@performed_render = true
|
||||||
|
|
||||||
response.headers['Status'] = interpret_status(status || DEFAULT_RENDER_STATUS_CODE)
|
response.status = interpret_status(status || DEFAULT_RENDER_STATUS_CODE)
|
||||||
|
|
||||||
if append_response
|
if append_response
|
||||||
response.body ||= ''
|
response.body ||= ''
|
||||||
|
|
@ -1191,6 +1267,16 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_render_arguments(options, extra_options, has_block)
|
||||||
|
if options && (has_block && options != :update) && !options.is_a?(String) && !options.is_a?(Hash) && !options.is_a?(Symbol)
|
||||||
|
raise RenderError, "You called render with invalid options : #{options.inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
|
if !extra_options.is_a?(Hash)
|
||||||
|
raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def initialize_template_class(response)
|
def initialize_template_class(response)
|
||||||
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
|
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
|
||||||
response.template.helpers.send :include, self.class.master_helper_module
|
response.template.helpers.send :include, self.class.master_helper_module
|
||||||
|
|
@ -1199,7 +1285,7 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign_shortcuts(request, response)
|
def assign_shortcuts(request, response)
|
||||||
@_request, @_params, @_cookies = request, request.parameters, request.cookies
|
@_request, @_params = request, request.parameters
|
||||||
|
|
||||||
@_response = response
|
@_response = response
|
||||||
@_response.session = request.session
|
@_response.session = request.session
|
||||||
|
|
@ -1217,7 +1303,6 @@ module ActionController #:nodoc:
|
||||||
def log_processing
|
def log_processing
|
||||||
if logger && logger.info?
|
if logger && logger.info?
|
||||||
log_processing_for_request_id
|
log_processing_for_request_id
|
||||||
log_processing_for_session_id
|
|
||||||
log_processing_for_parameters
|
log_processing_for_parameters
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -1230,13 +1315,6 @@ module ActionController #:nodoc:
|
||||||
logger.info(request_id)
|
logger.info(request_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def log_processing_for_session_id
|
|
||||||
if @_session && @_session.respond_to?(:session_id) && @_session.respond_to?(:dbman) &&
|
|
||||||
!@_session.dbman.is_a?(CGI::Session::CookieStore)
|
|
||||||
logger.info " Session ID: #{@_session.session_id}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def log_processing_for_parameters
|
def log_processing_for_parameters
|
||||||
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
|
parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
|
||||||
parameters = parameters.except!(:controller, :action, :format, :_method)
|
parameters = parameters.except!(:controller, :action, :format, :_method)
|
||||||
|
|
@ -1255,10 +1333,17 @@ module ActionController #:nodoc:
|
||||||
elsif respond_to? :method_missing
|
elsif respond_to? :method_missing
|
||||||
method_missing action_name
|
method_missing action_name
|
||||||
default_render unless performed?
|
default_render unless performed?
|
||||||
elsif template_exists?
|
|
||||||
default_render
|
|
||||||
else
|
else
|
||||||
raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller
|
begin
|
||||||
|
default_render
|
||||||
|
rescue ActionView::MissingTemplate => e
|
||||||
|
# Was the implicit template missing, or was it another template?
|
||||||
|
if e.path == default_template_name
|
||||||
|
raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence(:locale => :en)}", caller
|
||||||
|
else
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -1270,11 +1355,6 @@ module ActionController #:nodoc:
|
||||||
@action_name = (params['action'] || 'index')
|
@action_name = (params['action'] || 'index')
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign_default_content_type_and_charset
|
|
||||||
response.assign_default_content_type_and_charset!
|
|
||||||
end
|
|
||||||
deprecate :assign_default_content_type_and_charset => :'response.assign_default_content_type_and_charset!'
|
|
||||||
|
|
||||||
def action_methods
|
def action_methods
|
||||||
self.class.action_methods
|
self.class.action_methods
|
||||||
end
|
end
|
||||||
|
|
@ -1305,14 +1385,8 @@ module ActionController #:nodoc:
|
||||||
"#{request.protocol}#{request.host}#{request.request_uri}"
|
"#{request.protocol}#{request.host}#{request.request_uri}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def close_session
|
def default_template(action_name = self.action_name)
|
||||||
@_session.close if @_session && @_session.respond_to?(:close)
|
self.view_paths.find_template(default_template_name(action_name), default_template_format)
|
||||||
end
|
|
||||||
|
|
||||||
def template_exists?(template_name = default_template_name)
|
|
||||||
@template.send(:_pick_template, template_name) ? true : false
|
|
||||||
rescue ActionView::MissingTemplate
|
|
||||||
false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_template_name(action_name = self.action_name)
|
def default_template_name(action_name = self.action_name)
|
||||||
|
|
@ -1334,7 +1408,16 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_cleanup
|
def process_cleanup
|
||||||
close_session
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Base.class_eval do
|
||||||
|
[ Filters, Layout, Benchmarking, Rescue, Flash, MimeResponds, Helpers,
|
||||||
|
Cookies, Caching, Verification, Streaming, SessionManagement,
|
||||||
|
HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods,
|
||||||
|
RecordIdentifier, RequestForgeryProtection, Translation
|
||||||
|
].each do |mod|
|
||||||
|
include mod
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ module ActionController #:nodoc:
|
||||||
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
|
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
|
||||||
if logger && logger.level == log_level
|
if logger && logger.level == log_level
|
||||||
result = nil
|
result = nil
|
||||||
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
|
ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
|
||||||
logger.add(log_level, "#{title} (#{('%.1f' % (seconds * 1000))}ms)")
|
logger.add(log_level, "#{title} (#{('%.1f' % ms)}ms)")
|
||||||
result
|
result
|
||||||
else
|
else
|
||||||
yield
|
yield
|
||||||
|
|
@ -48,7 +48,7 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
render_output = nil
|
render_output = nil
|
||||||
@view_runtime = Benchmark::realtime { render_output = render_without_benchmark(options, extra_options, &block) }
|
@view_runtime = Benchmark.ms { render_output = render_without_benchmark(options, extra_options, &block) }
|
||||||
|
|
||||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||||
@db_rt_before_render = db_runtime
|
@db_rt_before_render = db_runtime
|
||||||
|
|
@ -65,11 +65,11 @@ module ActionController #:nodoc:
|
||||||
private
|
private
|
||||||
def perform_action_with_benchmark
|
def perform_action_with_benchmark
|
||||||
if logger
|
if logger
|
||||||
seconds = [ Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001 ].max
|
ms = [Benchmark.ms { perform_action_without_benchmark }, 0.01].max
|
||||||
logging_view = defined?(@view_runtime)
|
logging_view = defined?(@view_runtime)
|
||||||
logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
||||||
|
|
||||||
log_message = "Completed in #{sprintf("%.0f", seconds * 1000)}ms"
|
log_message = 'Completed in %.0fms' % ms
|
||||||
|
|
||||||
if logging_view || logging_active_record
|
if logging_view || logging_active_record
|
||||||
log_message << " ("
|
log_message << " ("
|
||||||
|
|
@ -83,25 +83,25 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
log_message << " | #{headers["Status"]}"
|
log_message << " | #{response.status}"
|
||||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
||||||
|
|
||||||
logger.info(log_message)
|
logger.info(log_message)
|
||||||
response.headers["X-Runtime"] = "#{sprintf("%.0f", seconds * 1000)}ms"
|
response.headers["X-Runtime"] = "%.0f" % ms
|
||||||
else
|
else
|
||||||
perform_action_without_benchmark
|
perform_action_without_benchmark
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def view_runtime
|
def view_runtime
|
||||||
"View: %.0f" % (@view_runtime * 1000)
|
"View: %.0f" % @view_runtime
|
||||||
end
|
end
|
||||||
|
|
||||||
def active_record_runtime
|
def active_record_runtime
|
||||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
||||||
db_runtime += @db_rt_before_render if @db_rt_before_render
|
db_runtime += @db_rt_before_render if @db_rt_before_render
|
||||||
db_runtime += @db_rt_after_render if @db_rt_after_render
|
db_runtime += @db_rt_after_render if @db_rt_after_render
|
||||||
"DB: %.0f" % (db_runtime * 1000)
|
"DB: %.0f" % db_runtime
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,6 @@ require 'fileutils'
|
||||||
require 'uri'
|
require 'uri'
|
||||||
require 'set'
|
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:
|
module ActionController #:nodoc:
|
||||||
# Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
|
# 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.
|
# around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment.
|
||||||
|
|
@ -29,8 +22,15 @@ module ActionController #:nodoc:
|
||||||
# ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
|
# ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
|
||||||
# ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
|
# ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
|
||||||
# ActionController::Base.cache_store = :mem_cache_store, "localhost"
|
# ActionController::Base.cache_store = :mem_cache_store, "localhost"
|
||||||
|
# ActionController::Base.cache_store = :mem_cache_store, Memcached::Rails.new("localhost:11211")
|
||||||
# ActionController::Base.cache_store = MyOwnStore.new("parameter")
|
# ActionController::Base.cache_store = MyOwnStore.new("parameter")
|
||||||
module Caching
|
module Caching
|
||||||
|
autoload :Actions, 'action_controller/caching/actions'
|
||||||
|
autoload :Fragments, 'action_controller/caching/fragments'
|
||||||
|
autoload :Pages, 'action_controller/caching/pages'
|
||||||
|
autoload :Sweeper, 'action_controller/caching/sweeper'
|
||||||
|
autoload :Sweeping, 'action_controller/caching/sweeping'
|
||||||
|
|
||||||
def self.included(base) #:nodoc:
|
def self.included(base) #:nodoc:
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
@@cache_store = nil
|
@@cache_store = nil
|
||||||
|
|
@ -42,7 +42,7 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
include Pages, Actions, Fragments
|
include Pages, Actions, Fragments
|
||||||
include Sweeping, SqlCache if defined?(ActiveRecord)
|
include Sweeping if defined?(ActiveRecord)
|
||||||
|
|
||||||
@@perform_caching = true
|
@@perform_caching = true
|
||||||
cattr_accessor :perform_caching
|
cattr_accessor :perform_caching
|
||||||
|
|
@ -63,7 +63,6 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def cache_configured?
|
def cache_configured?
|
||||||
self.class.cache_configured?
|
self.class.cache_configured?
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,9 @@ module ActionController #:nodoc:
|
||||||
filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
|
filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
|
||||||
|
|
||||||
cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
|
cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
|
||||||
around_filter(cache_filter, filter_options)
|
around_filter(filter_options) do |controller, action|
|
||||||
|
cache_filter.filter(controller, action)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -83,6 +85,12 @@ module ActionController #:nodoc:
|
||||||
@options = options
|
@options = options
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filter(controller, action)
|
||||||
|
should_continue = before(controller)
|
||||||
|
action.call if should_continue
|
||||||
|
after(controller)
|
||||||
|
end
|
||||||
|
|
||||||
def before(controller)
|
def before(controller)
|
||||||
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
|
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
|
||||||
if cache = controller.read_fragment(cache_path.path, @options[:store_options])
|
if cache = controller.read_fragment(cache_path.path, @options[:store_options])
|
||||||
|
|
@ -113,7 +121,7 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def caching_allowed(controller)
|
def caching_allowed(controller)
|
||||||
controller.request.get? && controller.response.headers['Status'].to_i == 200
|
controller.request.get? && controller.response.status.to_i == 200
|
||||||
end
|
end
|
||||||
|
|
||||||
def cache_layout?
|
def cache_layout?
|
||||||
|
|
@ -129,24 +137,23 @@ module ActionController #:nodoc:
|
||||||
attr_reader :path, :extension
|
attr_reader :path, :extension
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def path_for(controller, options, infer_extension=true)
|
def path_for(controller, options, infer_extension = true)
|
||||||
new(controller, options, infer_extension).path
|
new(controller, options, infer_extension).path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# When true, infer_extension will look up the cache path extension from the request's path & format.
|
# When true, infer_extension will look up the cache path extension from the request's path & format.
|
||||||
# This is desirable when reading and writing the cache, but not when expiring the cache - expire_action should expire the same files regardless of the request format.
|
# This is desirable when reading and writing the cache, but not when expiring the cache -
|
||||||
def initialize(controller, options = {}, infer_extension=true)
|
# expire_action should expire the same files regardless of the request format.
|
||||||
if infer_extension and options.is_a? Hash
|
def initialize(controller, options = {}, infer_extension = true)
|
||||||
request_extension = extract_extension(controller.request)
|
if infer_extension
|
||||||
options = options.reverse_merge(:format => request_extension)
|
extract_extension(controller.request)
|
||||||
|
options = options.reverse_merge(:format => @extension) if options.is_a?(Hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
path = controller.url_for(options).split('://').last
|
path = controller.url_for(options).split('://').last
|
||||||
normalize!(path)
|
normalize!(path)
|
||||||
if infer_extension
|
|
||||||
@extension = request_extension
|
|
||||||
add_extension!(path, @extension)
|
add_extension!(path, @extension)
|
||||||
end
|
|
||||||
@path = URI.unescape(path)
|
@path = URI.unescape(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -162,13 +169,7 @@ module ActionController #:nodoc:
|
||||||
def extract_extension(request)
|
def extract_extension(request)
|
||||||
# Don't want just what comes after the last '.' to accommodate multi part extensions
|
# Don't want just what comes after the last '.' to accommodate multi part extensions
|
||||||
# such as tar.gz.
|
# such as tar.gz.
|
||||||
extension = request.path[/^[^.]+\.(.+)$/, 1]
|
@extension = request.path[/^[^.]+\.(.+)$/, 1] || request.cache_format
|
||||||
|
|
||||||
# If there's no extension in the path, check request.format
|
|
||||||
if extension.nil?
|
|
||||||
extension = request.cache_format
|
|
||||||
end
|
|
||||||
extension
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ module ActionController #:nodoc:
|
||||||
|
|
||||||
# Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
# 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)
|
def write_fragment(key, content, options = nil)
|
||||||
return unless cache_configured?
|
return content unless cache_configured?
|
||||||
|
|
||||||
key = fragment_cache_key(key)
|
key = fragment_cache_key(key)
|
||||||
|
|
||||||
|
|
@ -83,15 +83,23 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Name can take one of three forms:
|
# Removes fragments from the cache.
|
||||||
# * 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 }
|
# +key+ can take one of three forms:
|
||||||
# * Regexp: Will destroy all the matched fragments, example:
|
# * String - This would normally take the form of a path, like
|
||||||
# %r{pages/\d*/notes}
|
# <tt>"pages/45/notes"</tt>.
|
||||||
# Ensure you do not specify start and finish in the regex (^$) because
|
# * Hash - Treated as an implicit call to +url_for+, like
|
||||||
# the actual filename matched looks like ./cache/filename/path.cache
|
# <tt>{:controller => "pages", :action => "notes", :id => 45}</tt>
|
||||||
# Regexp expiration is only supported on caches that can iterate over
|
# * Regexp - Will remove any fragment that matches, so
|
||||||
# all keys (unlike memcached).
|
# <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
|
||||||
|
# don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
|
||||||
|
# the actual filename matched looks like
|
||||||
|
# <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is
|
||||||
|
# only supported on caches that can iterate over all keys (unlike
|
||||||
|
# memcached).
|
||||||
|
#
|
||||||
|
# +options+ is passed through to the cache store's <tt>delete</tt>
|
||||||
|
# method (or <tt>delete_matched</tt>, for Regexp keys.)
|
||||||
def expire_fragment(key, options = nil)
|
def expire_fragment(key, options = nil)
|
||||||
return unless cache_configured?
|
return unless cache_configured?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,28 +33,26 @@ module ActionController #:nodoc:
|
||||||
#
|
#
|
||||||
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
|
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
|
||||||
# expired.
|
# 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
|
module Pages
|
||||||
def self.included(base) #:nodoc:
|
def self.included(base) #:nodoc:
|
||||||
base.extend(ClassMethods)
|
base.extend(ClassMethods)
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
@@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
|
@@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
|
||||||
|
##
|
||||||
|
# :singleton-method:
|
||||||
|
# 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.
|
||||||
cattr_accessor :page_cache_directory
|
cattr_accessor :page_cache_directory
|
||||||
|
|
||||||
@@page_cache_extension = '.html'
|
@@page_cache_extension = '.html'
|
||||||
|
##
|
||||||
|
# :singleton-method:
|
||||||
|
# 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.
|
||||||
cattr_accessor :page_cache_extension
|
cattr_accessor :page_cache_extension
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -147,7 +145,7 @@ module ActionController #:nodoc:
|
||||||
|
|
||||||
private
|
private
|
||||||
def caching_allowed
|
def caching_allowed
|
||||||
request.get? && response.headers['Status'].to_i == 200
|
request.get? && response.status.to_i == 200
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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
|
|
||||||
45
vendor/rails/actionpack/lib/action_controller/caching/sweeper.rb
vendored
Normal file
45
vendor/rails/actionpack/lib/action_controller/caching/sweeper.rb
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
require 'active_record'
|
||||||
|
|
||||||
|
module ActionController #:nodoc:
|
||||||
|
module Caching
|
||||||
|
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, &block)
|
||||||
|
return if @controller.nil?
|
||||||
|
@controller.__send__(method, *arguments, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -51,47 +51,5 @@ module ActionController #:nodoc:
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
require 'action_controller/cgi_ext/stdinput'
|
require 'action_controller/cgi_ext/stdinput'
|
||||||
require 'action_controller/cgi_ext/query_extension'
|
require 'action_controller/cgi_ext/query_extension'
|
||||||
require 'action_controller/cgi_ext/cookie'
|
require 'action_controller/cgi_ext/cookie'
|
||||||
require 'action_controller/cgi_ext/session'
|
|
||||||
|
|
||||||
class CGI #:nodoc:
|
class CGI #:nodoc:
|
||||||
include ActionController::CgiExt::Stdinput
|
include ActionController::CgiExt::Stdinput
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
require 'delegate'
|
||||||
|
|
||||||
CGI.module_eval { remove_const "Cookie" }
|
CGI.module_eval { remove_const "Cookie" }
|
||||||
|
|
||||||
# TODO: document how this differs from stdlib CGI::Cookie
|
# TODO: document how this differs from stdlib CGI::Cookie
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
require 'digest/md5'
|
|
||||||
require 'cgi/session'
|
|
||||||
require 'cgi/session/pstore'
|
|
||||||
|
|
||||||
class CGI #:nodoc:
|
|
||||||
# * Expose the CGI instance to session stores.
|
|
||||||
# * Don't require 'digest/md5' whenever a new session id is generated.
|
|
||||||
class Session #:nodoc:
|
|
||||||
def self.generate_unique_id(constant = nil)
|
|
||||||
ActiveSupport::SecureRandom.hex(16)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Make the CGI instance available to session stores.
|
|
||||||
attr_reader :cgi
|
|
||||||
attr_reader :dbman
|
|
||||||
alias_method :initialize_without_cgi_reader, :initialize
|
|
||||||
def initialize(cgi, options = {})
|
|
||||||
@cgi = cgi
|
|
||||||
initialize_without_cgi_reader(cgi, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
# Create a new session id.
|
|
||||||
def create_new_id
|
|
||||||
@new_session = true
|
|
||||||
self.class.generate_unique_id
|
|
||||||
end
|
|
||||||
|
|
||||||
# * Don't require 'digest/md5' whenever a new session is started.
|
|
||||||
class PStore #:nodoc:
|
|
||||||
def initialize(session, option={})
|
|
||||||
dir = option['tmpdir'] || Dir::tmpdir
|
|
||||||
prefix = option['prefix'] || ''
|
|
||||||
id = session.session_id
|
|
||||||
md5 = Digest::MD5.hexdigest(id)[0,16]
|
|
||||||
path = dir+"/"+prefix+md5
|
|
||||||
path.untaint
|
|
||||||
if File::exist?(path)
|
|
||||||
@hash = nil
|
|
||||||
else
|
|
||||||
unless session.new_session
|
|
||||||
raise CGI::Session::NoSession, "uninitialized session"
|
|
||||||
end
|
|
||||||
@hash = {}
|
|
||||||
end
|
|
||||||
@p = ::PStore.new(path)
|
|
||||||
@p.transaction do |p|
|
|
||||||
File.chmod(0600, p.path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,185 +1,77 @@
|
||||||
require 'action_controller/cgi_ext'
|
require 'action_controller/cgi_ext'
|
||||||
require 'action_controller/session/cookie_store'
|
|
||||||
|
|
||||||
module ActionController #:nodoc:
|
module ActionController #:nodoc:
|
||||||
class Base
|
class CGIHandler
|
||||||
# Process a request extracted from a CGI object and return a response. Pass false as <tt>session_options</tt> to disable
|
module ProperStream
|
||||||
# sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
|
def each
|
||||||
#
|
while line = gets
|
||||||
# * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
|
yield line
|
||||||
# (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
|
|
||||||
# lib/action_controller/session.
|
|
||||||
# * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
|
|
||||||
# * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ cookie, or
|
|
||||||
# automatically generated for a new session.
|
|
||||||
# * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
|
|
||||||
# exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
|
|
||||||
# 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
|
|
||||||
# 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.
|
|
||||||
# * <tt>:cookie_only</tt> - if +true+ (the default), session IDs will only be accepted from cookies and not from
|
|
||||||
# the query string or POST parameters. This protects against session fixation attacks.
|
|
||||||
def self.process_cgi(cgi = CGI.new, session_options = {})
|
|
||||||
new.process_cgi(cgi, session_options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_cgi(cgi, session_options = {}) #:nodoc:
|
|
||||||
process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class CgiRequest < AbstractRequest #:nodoc:
|
def read(*args)
|
||||||
attr_accessor :cgi, :session_options
|
if args.empty?
|
||||||
class SessionFixationAttempt < StandardError #:nodoc:
|
super || ""
|
||||||
end
|
|
||||||
|
|
||||||
DEFAULT_SESSION_OPTIONS = {
|
|
||||||
:database_manager => CGI::Session::CookieStore, # store data in cookie
|
|
||||||
:prefix => "ruby_sess.", # prefix session file names
|
|
||||||
:session_path => "/", # available to all paths in app
|
|
||||||
:session_key => "_session_id",
|
|
||||||
:cookie_only => true,
|
|
||||||
:session_http_only=> true
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize(cgi, session_options = {})
|
|
||||||
@cgi = cgi
|
|
||||||
@session_options = session_options
|
|
||||||
@env = @cgi.__send__(:env_table)
|
|
||||||
super()
|
|
||||||
end
|
|
||||||
|
|
||||||
def query_string
|
|
||||||
qs = @cgi.query_string if @cgi.respond_to?(:query_string)
|
|
||||||
if !qs.blank?
|
|
||||||
qs
|
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def body_stream #:nodoc:
|
|
||||||
@cgi.stdinput
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cookies
|
def self.dispatch_cgi(app, cgi, out = $stdout)
|
||||||
@cgi.cookies.freeze
|
env = cgi.__send__(:env_table)
|
||||||
end
|
env.delete "HTTP_CONTENT_LENGTH"
|
||||||
|
|
||||||
def session
|
cgi.stdinput.extend ProperStream
|
||||||
unless defined?(@session)
|
|
||||||
if @session_options == false
|
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
||||||
@session = Hash.new
|
|
||||||
else
|
env.update({
|
||||||
stale_session_check! do
|
"rack.version" => [0,1],
|
||||||
if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
|
"rack.input" => cgi.stdinput,
|
||||||
raise SessionFixationAttempt
|
"rack.errors" => $stderr,
|
||||||
end
|
"rack.multithread" => false,
|
||||||
case value = session_options_with_string_keys['new_session']
|
"rack.multiprocess" => true,
|
||||||
when true
|
"rack.run_once" => false,
|
||||||
@session = new_session
|
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
|
||||||
when false
|
})
|
||||||
|
|
||||||
|
env["QUERY_STRING"] ||= ""
|
||||||
|
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
|
||||||
|
env["REQUEST_PATH"] ||= "/"
|
||||||
|
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
|
||||||
|
|
||||||
|
status, headers, body = app.call(env)
|
||||||
begin
|
begin
|
||||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
out.binmode if out.respond_to?(:binmode)
|
||||||
# CGI::Session raises ArgumentError if 'new_session' == false
|
out.sync = false if out.respond_to?(:sync=)
|
||||||
# and no session cookie or query param is present.
|
|
||||||
rescue ArgumentError
|
headers['Status'] = status.to_s
|
||||||
@session = Hash.new
|
|
||||||
end
|
if headers.include?('Set-Cookie')
|
||||||
when nil
|
headers['cookie'] = headers.delete('Set-Cookie').split("\n")
|
||||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
|
||||||
else
|
|
||||||
raise ArgumentError, "Invalid new_session option: #{value}"
|
|
||||||
end
|
|
||||||
@session['__valid_session']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@session
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_session
|
out.write(cgi.header(headers))
|
||||||
@session.delete if defined?(@session) && @session.is_a?(CGI::Session)
|
|
||||||
@session = new_session
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_missing(method_id, *arguments)
|
body.each { |part|
|
||||||
@cgi.__send__(method_id, *arguments) rescue super
|
out.write part
|
||||||
|
out.flush if out.respond_to?(:flush)
|
||||||
|
}
|
||||||
|
ensure
|
||||||
|
body.close if body.respond_to?(:close)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
# Delete an old session if it exists then create a new one.
|
|
||||||
def new_session
|
|
||||||
if @session_options == false
|
|
||||||
Hash.new
|
|
||||||
else
|
|
||||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
|
|
||||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cookie_only?
|
class CgiRequest #:nodoc:
|
||||||
session_options_with_string_keys['cookie_only']
|
DEFAULT_SESSION_OPTIONS = {
|
||||||
end
|
:database_manager => nil,
|
||||||
|
:prefix => "ruby_sess.",
|
||||||
def stale_session_check!
|
:session_path => "/",
|
||||||
yield
|
:session_key => "_session_id",
|
||||||
rescue ArgumentError => argument_error
|
:cookie_only => true,
|
||||||
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
:session_http_only => true
|
||||||
begin
|
}
|
||||||
# Note that the regexp does not allow $1 to end with a ':'
|
|
||||||
$1.constantize
|
|
||||||
rescue LoadError, NameError => const_error
|
|
||||||
raise ActionController::SessionRestoreError, <<-end_msg
|
|
||||||
Session contains objects whose class definition isn\'t available.
|
|
||||||
Remember to require the classes for all objects kept in the session.
|
|
||||||
(Original exception: #{const_error.message} [#{const_error.class}])
|
|
||||||
end_msg
|
|
||||||
end
|
|
||||||
|
|
||||||
retry
|
|
||||||
else
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def session_options_with_string_keys
|
|
||||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class CgiResponse < AbstractResponse #:nodoc:
|
|
||||||
def initialize(cgi)
|
|
||||||
@cgi = cgi
|
|
||||||
super()
|
|
||||||
end
|
|
||||||
|
|
||||||
def out(output = $stdout)
|
|
||||||
output.binmode if output.respond_to?(:binmode)
|
|
||||||
output.sync = false if output.respond_to?(:sync=)
|
|
||||||
|
|
||||||
begin
|
|
||||||
output.write(@cgi.header(@headers))
|
|
||||||
|
|
||||||
if @cgi.__send__(:env_table)['REQUEST_METHOD'] == 'HEAD'
|
|
||||||
return
|
|
||||||
elsif @body.respond_to?(:call)
|
|
||||||
# Flush the output now in case the @body Proc uses
|
|
||||||
# #syswrite.
|
|
||||||
output.flush if output.respond_to?(:flush)
|
|
||||||
@body.call(self, output)
|
|
||||||
else
|
|
||||||
output.write(@body)
|
|
||||||
end
|
|
||||||
|
|
||||||
output.flush if output.respond_to?(:flush)
|
|
||||||
rescue Errno::EPIPE, Errno::ECONNRESET
|
|
||||||
# lost connection to parent process, ignore output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
module ActionController #:nodoc:
|
|
||||||
# Components allow you to call other actions for their rendered response while executing another action. You can either delegate
|
|
||||||
# the entire response rendering or you can mix a partial response in with your other content.
|
|
||||||
#
|
|
||||||
# class WeblogController < ActionController::Base
|
|
||||||
# # Performs a method and then lets hello_world output its render
|
|
||||||
# def delegate_action
|
|
||||||
# do_other_stuff_before_hello_world
|
|
||||||
# render_component :controller => "greeter", :action => "hello_world", :params => { :person => "david" }
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class GreeterController < ActionController::Base
|
|
||||||
# def hello_world
|
|
||||||
# render :text => "#{params[:person]} says, Hello World!"
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# The same can be done in a view to do a partial rendering:
|
|
||||||
#
|
|
||||||
# Let's see a greeting:
|
|
||||||
# <%= render_component :controller => "greeter", :action => "hello_world" %>
|
|
||||||
#
|
|
||||||
# It is also possible to specify the controller as a class constant, bypassing the inflector
|
|
||||||
# code to compute the controller class at runtime:
|
|
||||||
#
|
|
||||||
# <%= render_component :controller => GreeterController, :action => "hello_world" %>
|
|
||||||
#
|
|
||||||
# == When to use components
|
|
||||||
#
|
|
||||||
# Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and
|
|
||||||
# conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead,
|
|
||||||
# reserve components to those rare cases where you truly have reusable view and controller elements that can be employed
|
|
||||||
# across many applications at once.
|
|
||||||
#
|
|
||||||
# So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters.
|
|
||||||
module Components
|
|
||||||
def self.included(base) #:nodoc:
|
|
||||||
base.class_eval do
|
|
||||||
include InstanceMethods
|
|
||||||
include ActiveSupport::Deprecation
|
|
||||||
extend ClassMethods
|
|
||||||
helper HelperMethods
|
|
||||||
|
|
||||||
# If this controller was instantiated to process a component request,
|
|
||||||
# +parent_controller+ points to the instantiator of this controller.
|
|
||||||
attr_accessor :parent_controller
|
|
||||||
|
|
||||||
alias_method_chain :process_cleanup, :components
|
|
||||||
alias_method_chain :set_session_options, :components
|
|
||||||
alias_method_chain :flash, :components
|
|
||||||
|
|
||||||
alias_method :component_request?, :parent_controller
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
# Track parent controller to identify component requests
|
|
||||||
def process_with_components(request, response, parent_controller = nil) #:nodoc:
|
|
||||||
controller = new
|
|
||||||
controller.parent_controller = parent_controller
|
|
||||||
controller.process(request, response)
|
|
||||||
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:
|
|
||||||
flash.discard if component_request?
|
|
||||||
process_without_components(request, response, method, *arguments)
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
# Renders the component specified as the response for the current method
|
|
||||||
def render_component(options) #:doc:
|
|
||||||
component_logging(options) do
|
|
||||||
render_for_text(component_response(options, true).body, response.headers["Status"])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
deprecate :render_component => "Please install render_component plugin from http://github.com/rails/render_component/tree/master"
|
|
||||||
|
|
||||||
# Returns the component response as a string
|
|
||||||
def render_component_as_string(options) #:doc:
|
|
||||||
component_logging(options) do
|
|
||||||
response = component_response(options, false)
|
|
||||||
|
|
||||||
if redirected = response.redirected_to
|
|
||||||
render_component_as_string(redirected)
|
|
||||||
else
|
|
||||||
response.body
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
deprecate :render_component_as_string => "Please install render_component plugin from http://github.com/rails/render_component/tree/master"
|
|
||||||
|
|
||||||
def flash_with_components(refresh = false) #:nodoc:
|
|
||||||
if !defined?(@_flash) || refresh
|
|
||||||
@_flash =
|
|
||||||
if defined?(@parent_controller)
|
|
||||||
@parent_controller.flash
|
|
||||||
else
|
|
||||||
flash_without_components
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@_flash
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def component_response(options, reuse_response)
|
|
||||||
klass = component_class(options)
|
|
||||||
request = request_for_component(klass.controller_name, options)
|
|
||||||
new_response = reuse_response ? response : response.dup
|
|
||||||
|
|
||||||
klass.process_with_components(request, new_response, self)
|
|
||||||
end
|
|
||||||
|
|
||||||
# determine the controller class for the component request
|
|
||||||
def component_class(options)
|
|
||||||
if controller = options[:controller]
|
|
||||||
controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize
|
|
||||||
else
|
|
||||||
self.class
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Create a new request object based on the current request.
|
|
||||||
# The new request inherits the session from the current request,
|
|
||||||
# bypassing any session options set for the component controller's class
|
|
||||||
def request_for_component(controller_name, options)
|
|
||||||
new_request = request.dup
|
|
||||||
new_request.session = request.session
|
|
||||||
|
|
||||||
new_request.instance_variable_set(
|
|
||||||
:@parameters,
|
|
||||||
(options[:params] || {}).with_indifferent_access.update(
|
|
||||||
"controller" => controller_name, "action" => options[:action], "id" => options[:id]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
new_request
|
|
||||||
end
|
|
||||||
|
|
||||||
def component_logging(options)
|
|
||||||
if logger
|
|
||||||
logger.info "Start rendering component (#{options.inspect}): "
|
|
||||||
result = yield
|
|
||||||
logger.info "\n\nEnd of component rendering"
|
|
||||||
result
|
|
||||||
else
|
|
||||||
yield
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_session_options_with_components(request)
|
|
||||||
set_session_options_without_components(request) unless component_request?
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_cleanup_with_components
|
|
||||||
process_cleanup_without_components unless component_request?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -41,7 +41,7 @@ module ActionController #:nodoc:
|
||||||
# * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
|
# * <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.
|
# * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
|
||||||
# Default is +false+.
|
# Default is +false+.
|
||||||
# * <tt>:http_only</tt> - Whether this cookie is accessible via scripting or
|
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
|
||||||
# only HTTP. Defaults to +false+.
|
# only HTTP. Defaults to +false+.
|
||||||
module Cookies
|
module Cookies
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
|
|
@ -51,7 +51,7 @@ module ActionController #:nodoc:
|
||||||
protected
|
protected
|
||||||
# Returns the cookie container, which operates as described above.
|
# Returns the cookie container, which operates as described above.
|
||||||
def cookies
|
def cookies
|
||||||
CookieJar.new(self)
|
@cookies ||= CookieJar.new(self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -64,43 +64,32 @@ module ActionController #:nodoc:
|
||||||
|
|
||||||
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
|
# Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
|
||||||
def [](name)
|
def [](name)
|
||||||
cookie = @cookies[name.to_s]
|
super(name.to_s)
|
||||||
if cookie && cookie.respond_to?(:value)
|
|
||||||
cookie.size > 1 ? cookie.value : cookie.value[0]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the cookie named +name+. The second argument may be the very cookie
|
# Sets the cookie named +name+. The second argument may be the very cookie
|
||||||
# value, or a hash of options as documented above.
|
# value, or a hash of options as documented above.
|
||||||
def []=(name, options)
|
def []=(key, options)
|
||||||
if options.is_a?(Hash)
|
if options.is_a?(Hash)
|
||||||
options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
|
options.symbolize_keys!
|
||||||
options["name"] = name.to_s
|
|
||||||
else
|
else
|
||||||
options = { "name" => name.to_s, "value" => options }
|
options = { :value => options }
|
||||||
end
|
end
|
||||||
|
|
||||||
set_cookie(options)
|
options[:path] = "/" unless options.has_key?(:path)
|
||||||
|
super(key.to_s, options[:value])
|
||||||
|
@controller.response.set_cookie(key, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Removes the cookie on the client machine by setting the value to an empty string
|
# Removes the cookie on the client machine by setting the value to an empty string
|
||||||
# and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
|
# 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>.
|
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
|
||||||
def delete(name, options = {})
|
def delete(key, options = {})
|
||||||
options.stringify_keys!
|
options.symbolize_keys!
|
||||||
set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0)))
|
options[:path] = "/" unless options.has_key?(:path)
|
||||||
end
|
value = super(key.to_s)
|
||||||
|
@controller.response.delete_cookie(key, options)
|
||||||
private
|
value
|
||||||
# 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)
|
|
||||||
@controller.logger.info "Cookie set: #{cookie}" unless @controller.logger.nil?
|
|
||||||
@controller.response.headers["cookie"] << cookie
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,16 @@ module ActionController
|
||||||
# Dispatches requests to the appropriate controller and takes care of
|
# Dispatches requests to the appropriate controller and takes care of
|
||||||
# reloading the app after each request when Dependencies.load? is true.
|
# reloading the app after each request when Dependencies.load? is true.
|
||||||
class Dispatcher
|
class Dispatcher
|
||||||
@@guard = Mutex.new
|
@@cache_classes = true
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def define_dispatcher_callbacks(cache_classes)
|
def define_dispatcher_callbacks(cache_classes)
|
||||||
|
@@cache_classes = cache_classes
|
||||||
unless cache_classes
|
unless cache_classes
|
||||||
# Development mode callbacks
|
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
|
||||||
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
|
end
|
||||||
|
|
||||||
if defined?(ActiveRecord)
|
if defined?(ActiveRecord)
|
||||||
after_dispatch :checkin_connections
|
|
||||||
to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers }
|
to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -33,8 +22,7 @@ module ActionController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Backward-compatible class method takes CGI-specific args. Deprecated
|
# DEPRECATE: Remove CGI support
|
||||||
# in favor of Dispatcher.new(output, request, response).dispatch.
|
|
||||||
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
|
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
|
||||||
new(output).dispatch_cgi(cgi, session_options)
|
new(output).dispatch_cgi(cgi, session_options)
|
||||||
end
|
end
|
||||||
|
|
@ -53,144 +41,93 @@ module ActionController
|
||||||
@prepare_dispatch_callbacks.replace_or_append!(callback)
|
@prepare_dispatch_callbacks.replace_or_append!(callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
# If the block raises, send status code as a last-ditch response.
|
def run_prepare_callbacks
|
||||||
def failsafe_response(fallback_output, status, originating_exception = nil)
|
if defined?(Rails) && Rails.logger
|
||||||
yield
|
logger = Rails.logger
|
||||||
rescue Exception => exception
|
|
||||||
begin
|
|
||||||
log_failsafe_exception(status, originating_exception || exception)
|
|
||||||
body = failsafe_response_body(status)
|
|
||||||
fallback_output.write "Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}"
|
|
||||||
nil
|
|
||||||
rescue Exception => failsafe_error # Logger or IO errors
|
|
||||||
$stderr.puts "Error during failsafe response: #{failsafe_error}"
|
|
||||||
$stderr.puts "(originally #{originating_exception})" if originating_exception
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def failsafe_response_body(status)
|
|
||||||
error_path = "#{error_file_path}/#{status.to_s[0..3]}.html"
|
|
||||||
|
|
||||||
if File.exist?(error_path)
|
|
||||||
File.read(error_path)
|
|
||||||
else
|
else
|
||||||
"<html><body><h1>#{status}</h1></body></html>"
|
logger = Logger.new($stderr)
|
||||||
|
end
|
||||||
|
|
||||||
|
new(logger).send :run_callbacks, :prepare_dispatch
|
||||||
|
end
|
||||||
|
|
||||||
|
def reload_application
|
||||||
|
# Run prepare callbacks before every request in development mode
|
||||||
|
run_prepare_callbacks
|
||||||
|
|
||||||
|
Routing::Routes.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
def cleanup_application
|
||||||
|
# Cleanup the application before processing the current request.
|
||||||
|
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
|
||||||
|
ActiveSupport::Dependencies.clear
|
||||||
|
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def log_failsafe_exception(status, exception)
|
cattr_accessor :middleware
|
||||||
message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: #{status}\n"
|
self.middleware = MiddlewareStack.new do |middleware|
|
||||||
message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
|
middlewares = File.join(File.dirname(__FILE__), "middlewares.rb")
|
||||||
failsafe_logger.fatal message
|
middleware.instance_eval(File.read(middlewares))
|
||||||
end
|
end
|
||||||
|
|
||||||
def failsafe_logger
|
|
||||||
if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
|
|
||||||
::RAILS_DEFAULT_LOGGER
|
|
||||||
else
|
|
||||||
Logger.new($stderr)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
cattr_accessor :error_file_path
|
|
||||||
self.error_file_path = Rails.public_path if defined?(Rails.public_path)
|
|
||||||
|
|
||||||
include ActiveSupport::Callbacks
|
include ActiveSupport::Callbacks
|
||||||
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
||||||
|
|
||||||
|
# DEPRECATE: Remove arguments, since they are only used by CGI
|
||||||
def initialize(output = $stdout, request = nil, response = nil)
|
def initialize(output = $stdout, request = nil, response = nil)
|
||||||
@output, @request, @response = output, request, response
|
@output = output
|
||||||
|
build_middleware_stack if @@cache_classes
|
||||||
end
|
end
|
||||||
|
|
||||||
def dispatch_unlocked
|
def dispatch
|
||||||
begin
|
begin
|
||||||
run_callbacks :before_dispatch
|
run_callbacks :before_dispatch
|
||||||
handle_request
|
Routing::Routes.call(@env)
|
||||||
rescue Exception => exception
|
rescue Exception => exception
|
||||||
failsafe_rescue exception
|
if controller ||= (::ApplicationController rescue Base)
|
||||||
|
controller.call_with_exception(@env, exception).to_a
|
||||||
|
else
|
||||||
|
raise exception
|
||||||
|
end
|
||||||
ensure
|
ensure
|
||||||
run_callbacks :after_dispatch, :enumerator => :reverse_each
|
run_callbacks :after_dispatch, :enumerator => :reverse_each
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def dispatch
|
# DEPRECATE: Remove CGI support
|
||||||
if ActionController::Base.allow_concurrency
|
|
||||||
dispatch_unlocked
|
|
||||||
else
|
|
||||||
@@guard.synchronize do
|
|
||||||
dispatch_unlocked
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def dispatch_cgi(cgi, session_options)
|
def dispatch_cgi(cgi, session_options)
|
||||||
if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new }
|
CGIHandler.dispatch_cgi(self, cgi, @output)
|
||||||
@request = CgiRequest.new(cgi, session_options)
|
|
||||||
@response = CgiResponse.new(cgi)
|
|
||||||
dispatch
|
|
||||||
end
|
|
||||||
rescue Exception => exception
|
|
||||||
failsafe_rescue exception
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
@request = RackRequest.new(env)
|
if @@cache_classes
|
||||||
@response = RackResponse.new(@request)
|
@app.call(env)
|
||||||
|
else
|
||||||
|
Reloader.run do
|
||||||
|
# When class reloading is turned on, we will want to rebuild the
|
||||||
|
# middleware stack every time we process a request. If we don't
|
||||||
|
# rebuild the middleware stack, then the stack may contain references
|
||||||
|
# to old classes metal classes, which will b0rk class reloading.
|
||||||
|
build_middleware_stack
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def _call(env)
|
||||||
|
@env = env
|
||||||
dispatch
|
dispatch
|
||||||
end
|
end
|
||||||
|
|
||||||
def reload_application
|
|
||||||
# Run prepare callbacks before every request in development mode
|
|
||||||
run_callbacks :prepare_dispatch
|
|
||||||
|
|
||||||
Routing::Routes.reload
|
|
||||||
ActionController::Base.view_paths.reload!
|
|
||||||
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear
|
|
||||||
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
|
|
||||||
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
|
|
||||||
ActiveSupport::Dependencies.clear
|
|
||||||
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
|
|
||||||
end
|
|
||||||
|
|
||||||
def flush_logger
|
def flush_logger
|
||||||
Base.logger.flush
|
Base.logger.flush
|
||||||
end
|
end
|
||||||
|
|
||||||
def mark_as_test_request!
|
private
|
||||||
@test_request = true
|
def build_middleware_stack
|
||||||
self
|
@app = @@middleware.build(lambda { |env| self.dup._call(env) })
|
||||||
end
|
|
||||||
|
|
||||||
def test_request?
|
|
||||||
@test_request
|
|
||||||
end
|
|
||||||
|
|
||||||
def checkin_connections
|
|
||||||
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
|
|
||||||
return if test_request?
|
|
||||||
ActiveRecord::Base.clear_active_connections!
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
def handle_request
|
|
||||||
@controller = Routing::Routes.recognize(@request)
|
|
||||||
@controller.process(@request, @response).out(@output)
|
|
||||||
end
|
|
||||||
|
|
||||||
def failsafe_rescue(exception)
|
|
||||||
self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
|
|
||||||
if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base
|
|
||||||
@controller.process_with_exception(@request, @response, exception).out(@output)
|
|
||||||
else
|
|
||||||
raise exception
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
86
vendor/rails/actionpack/lib/action_controller/failsafe.rb
vendored
Normal file
86
vendor/rails/actionpack/lib/action_controller/failsafe.rb
vendored
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
require 'erb'
|
||||||
|
|
||||||
|
module ActionController
|
||||||
|
# The Failsafe middleware is usually the top-most middleware in the Rack
|
||||||
|
# middleware chain. It returns the underlying middleware's response, but if
|
||||||
|
# the underlying middle raises an exception then Failsafe will log the
|
||||||
|
# exception into the Rails log file, and will attempt to return an error
|
||||||
|
# message response.
|
||||||
|
#
|
||||||
|
# Failsafe is a last resort for logging errors and for telling the HTTP
|
||||||
|
# client that something went wrong. Do not confuse this with the
|
||||||
|
# ActionController::Rescue module, which is responsible for catching
|
||||||
|
# exceptions at deeper levels. Unlike Failsafe, which is as simple as
|
||||||
|
# possible, Rescue provides features that allow developers to hook into
|
||||||
|
# the error handling logic, and can customize the error message response
|
||||||
|
# based on the HTTP client's IP.
|
||||||
|
class Failsafe
|
||||||
|
cattr_accessor :error_file_path
|
||||||
|
self.error_file_path = Rails.public_path if defined?(Rails.public_path)
|
||||||
|
|
||||||
|
def initialize(app)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
@app.call(env)
|
||||||
|
rescue Exception => exception
|
||||||
|
# Reraise exception in test environment
|
||||||
|
if defined?(Rails) && Rails.env.test?
|
||||||
|
raise exception
|
||||||
|
else
|
||||||
|
failsafe_response(exception)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def failsafe_response(exception)
|
||||||
|
log_failsafe_exception(exception)
|
||||||
|
[500, {'Content-Type' => 'text/html'}, [failsafe_response_body]]
|
||||||
|
rescue Exception => failsafe_error # Logger or IO errors
|
||||||
|
$stderr.puts "Error during failsafe response: #{failsafe_error}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def failsafe_response_body
|
||||||
|
error_template_path = "#{self.class.error_file_path}/500.html"
|
||||||
|
if File.exist?(error_template_path)
|
||||||
|
begin
|
||||||
|
result = render_template(error_template_path)
|
||||||
|
rescue Exception
|
||||||
|
result = nil
|
||||||
|
end
|
||||||
|
else
|
||||||
|
result = nil
|
||||||
|
end
|
||||||
|
if result.nil?
|
||||||
|
result = "<html><body><h1>500 Internal Server Error</h1>" <<
|
||||||
|
"If you are the administrator of this website, then please read this web " <<
|
||||||
|
"application's log file to find out what went wrong.</body></html>"
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
# The default 500.html uses the h() method.
|
||||||
|
def h(text) # :nodoc:
|
||||||
|
ERB::Util.h(text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_template(filename)
|
||||||
|
ERB.new(File.read(filename)).result(binding)
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_failsafe_exception(exception)
|
||||||
|
message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: 500 Internal Server Error\n"
|
||||||
|
message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
|
||||||
|
failsafe_logger.fatal(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def failsafe_logger
|
||||||
|
if defined?(Rails) && Rails.logger
|
||||||
|
Rails.logger
|
||||||
|
else
|
||||||
|
Logger.new($stderr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -4,20 +4,22 @@ module ActionController #:nodoc:
|
||||||
# action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
|
# action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
|
||||||
# then expose the flash to its template. Actually, that exposure is automatically done. Example:
|
# then expose the flash to its template. Actually, that exposure is automatically done. Example:
|
||||||
#
|
#
|
||||||
# class WeblogController < ActionController::Base
|
# class PostsController < ActionController::Base
|
||||||
# def create
|
# def create
|
||||||
# # save post
|
# # save post
|
||||||
# flash[:notice] = "Successfully created post"
|
# flash[:notice] = "Successfully created post"
|
||||||
# redirect_to :action => "display", :params => { :id => post.id }
|
# redirect_to posts_path(@post)
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# def display
|
# def show
|
||||||
# # doesn't need to assign the flash notice to the template, that's done automatically
|
# # doesn't need to assign the flash notice to the template, that's done automatically
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# display.erb
|
# show.html.erb
|
||||||
# <% if flash[:notice] %><div class="notice"><%= flash[:notice] %></div><% end %>
|
# <% if flash[:notice] %>
|
||||||
|
# <div class="notice"><%= flash[:notice] %></div>
|
||||||
|
# <% end %>
|
||||||
#
|
#
|
||||||
# This example just places a string in the flash, but you can put any object in there. And of course, you can put as
|
# This example just places a string in the flash, but you can put any object in there. And of course, you can put as
|
||||||
# many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
|
# many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
|
||||||
|
|
@ -27,12 +29,11 @@ module ActionController #:nodoc:
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
include InstanceMethods
|
include InstanceMethods
|
||||||
alias_method_chain :assign_shortcuts, :flash
|
alias_method_chain :perform_action, :flash
|
||||||
alias_method_chain :reset_session, :flash
|
alias_method_chain :reset_session, :flash
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class FlashNow #:nodoc:
|
class FlashNow #:nodoc:
|
||||||
def initialize(flash)
|
def initialize(flash)
|
||||||
@flash = flash
|
@flash = flash
|
||||||
|
|
@ -119,6 +120,11 @@ module ActionController #:nodoc:
|
||||||
(@used.keys - keys).each{ |k| @used.delete(k) }
|
(@used.keys - keys).each{ |k| @used.delete(k) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def store(session, key = "flash")
|
||||||
|
return if self.empty?
|
||||||
|
session[key] = self
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
|
# Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
|
||||||
# use() # marks the entire flash as used
|
# use() # marks the entire flash as used
|
||||||
|
|
@ -136,37 +142,30 @@ module ActionController #:nodoc:
|
||||||
|
|
||||||
module InstanceMethods #:nodoc:
|
module InstanceMethods #:nodoc:
|
||||||
protected
|
protected
|
||||||
def reset_session_with_flash
|
def perform_action_with_flash
|
||||||
reset_session_without_flash
|
perform_action_without_flash
|
||||||
|
if defined? @_flash
|
||||||
|
@_flash.store(session)
|
||||||
remove_instance_variable(:@_flash)
|
remove_instance_variable(:@_flash)
|
||||||
flash(:refresh)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
|
def reset_session_with_flash
|
||||||
# <tt>flash["notice"] = "hello"</tt> to put a new one.
|
reset_session_without_flash
|
||||||
# Note that if sessions are disabled only flash.now will work.
|
remove_instance_variable(:@_flash) if defined? @_flash
|
||||||
def flash(refresh = false) #:doc:
|
|
||||||
if !defined?(@_flash) || refresh
|
|
||||||
@_flash =
|
|
||||||
if session.is_a?(Hash)
|
|
||||||
# don't put flash in session if disabled
|
|
||||||
FlashHash.new
|
|
||||||
else
|
|
||||||
# otherwise, session is a CGI::Session or a TestSession
|
|
||||||
# so make sure it gets retrieved from/saved to session storage after request processing
|
|
||||||
session["flash"] ||= FlashHash.new
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
|
||||||
|
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
|
||||||
|
# to put a new one.
|
||||||
|
def flash #:doc:
|
||||||
|
if !defined?(@_flash)
|
||||||
|
@_flash = session["flash"] || FlashHash.new
|
||||||
|
@_flash.sweep
|
||||||
end
|
end
|
||||||
|
|
||||||
@_flash
|
@_flash
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
def assign_shortcuts_with_flash(request, response) #:nodoc:
|
|
||||||
assign_shortcuts_without_flash(request, response)
|
|
||||||
flash(:refresh)
|
|
||||||
flash.sweep if @_session && !component_request?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
|
require 'active_support/dependencies'
|
||||||
|
|
||||||
# FIXME: helper { ... } is broken on Ruby 1.9
|
# FIXME: helper { ... } is broken on Ruby 1.9
|
||||||
module ActionController #:nodoc:
|
module ActionController #:nodoc:
|
||||||
module Helpers #:nodoc:
|
module Helpers #:nodoc:
|
||||||
HELPERS_DIR = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers")
|
|
||||||
|
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
# Initialize the base module to aggregate its helpers.
|
# Initialize the base module to aggregate its helpers.
|
||||||
base.class_inheritable_accessor :master_helper_module
|
base.class_inheritable_accessor :master_helper_module
|
||||||
base.master_helper_module = Module.new
|
base.master_helper_module = Module.new
|
||||||
|
|
||||||
|
# Set the default directory for helpers
|
||||||
|
base.class_inheritable_accessor :helpers_dir
|
||||||
|
base.helpers_dir = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers")
|
||||||
|
|
||||||
# Extend base with class methods to declare helpers.
|
# Extend base with class methods to declare helpers.
|
||||||
base.extend(ClassMethods)
|
base.extend(ClassMethods)
|
||||||
|
|
||||||
|
|
@ -88,8 +92,8 @@ module ActionController #:nodoc:
|
||||||
# When the argument is a module it will be included directly in the template class.
|
# When the argument is a module it will be included directly in the template class.
|
||||||
# helper FooHelper # => includes FooHelper
|
# helper FooHelper # => includes FooHelper
|
||||||
#
|
#
|
||||||
# When the argument is the symbol <tt>:all</tt>, the controller will include all helpers from
|
# When the argument is the symbol <tt>:all</tt>, the controller will include all helpers beneath
|
||||||
# <tt>app/helpers/**/*.rb</tt> under RAILS_ROOT.
|
# <tt>ActionController::Base.helpers_dir</tt> (defaults to <tt>app/helpers/**/*.rb</tt> under RAILS_ROOT).
|
||||||
# helper :all
|
# helper :all
|
||||||
#
|
#
|
||||||
# Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
|
# Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
|
||||||
|
|
@ -159,9 +163,9 @@ module ActionController #:nodoc:
|
||||||
def helper_method(*methods)
|
def helper_method(*methods)
|
||||||
methods.flatten.each do |method|
|
methods.flatten.each do |method|
|
||||||
master_helper_module.module_eval <<-end_eval
|
master_helper_module.module_eval <<-end_eval
|
||||||
def #{method}(*args, &block)
|
def #{method}(*args, &block) # def current_user(*args, &block)
|
||||||
controller.send(%(#{method}), *args, &block)
|
controller.send(%(#{method}), *args, &block) # controller.send(%(current_user), *args, &block)
|
||||||
end
|
end # end
|
||||||
end_eval
|
end_eval
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -213,8 +217,8 @@ module ActionController #:nodoc:
|
||||||
|
|
||||||
# Extract helper names from files in app/helpers/**/*.rb
|
# Extract helper names from files in app/helpers/**/*.rb
|
||||||
def all_application_helpers
|
def all_application_helpers
|
||||||
extract = /^#{Regexp.quote(HELPERS_DIR)}\/?(.*)_helper.rb$/
|
extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/
|
||||||
Dir["#{HELPERS_DIR}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
|
Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ module ActionController
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
#
|
|
||||||
# In your integration tests, you can do something like this:
|
# In your integration tests, you can do something like this:
|
||||||
#
|
#
|
||||||
# def test_access_granted_from_xml
|
# def test_access_granted_from_xml
|
||||||
|
|
@ -67,6 +66,38 @@ module ActionController
|
||||||
# assert_equal 200, status
|
# assert_equal 200, status
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
|
# Simple Digest example:
|
||||||
|
#
|
||||||
|
# require 'digest/md5'
|
||||||
|
# class PostsController < ApplicationController
|
||||||
|
# REALM = "SuperSecret"
|
||||||
|
# USERS = {"dhh" => "secret", #plain text password
|
||||||
|
# "dap" => Digest:MD5::hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password
|
||||||
|
#
|
||||||
|
# before_filter :authenticate, :except => [:index]
|
||||||
|
#
|
||||||
|
# def index
|
||||||
|
# render :text => "Everyone can see me!"
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def edit
|
||||||
|
# render :text => "I'm only accessible if you know the password"
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# private
|
||||||
|
# def authenticate
|
||||||
|
# authenticate_or_request_with_http_digest(REALM) do |username|
|
||||||
|
# USERS[username]
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password or the ha1 digest hash so the framework can appropriately
|
||||||
|
# hash to check the user's credentials. Returning +nil+ will cause authentication to fail.
|
||||||
|
# Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
|
||||||
|
# the password file or database is compromised, the attacker would be able to use the ha1 hash to
|
||||||
|
# authenticate as the user at this +realm+, but would not have the user's password to try using at
|
||||||
|
# other sites.
|
||||||
#
|
#
|
||||||
# On shared hosts, Apache sometimes doesn't pass authentication headers to
|
# On shared hosts, Apache sometimes doesn't pass authentication headers to
|
||||||
# FCGI instances. If your environment matches this description and you cannot
|
# FCGI instances. If your environment matches this description and you cannot
|
||||||
|
|
@ -108,7 +139,7 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def decode_credentials(request)
|
def decode_credentials(request)
|
||||||
ActiveSupport::Base64.decode64(authorization(request).split.last || '')
|
ActiveSupport::Base64.decode64(authorization(request).split(' ', 2).last || '')
|
||||||
end
|
end
|
||||||
|
|
||||||
def encode_credentials(user_name, password)
|
def encode_credentials(user_name, password)
|
||||||
|
|
@ -120,5 +151,159 @@ module ActionController
|
||||||
controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
|
controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Digest
|
||||||
|
extend self
|
||||||
|
|
||||||
|
module ControllerMethods
|
||||||
|
def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
|
||||||
|
authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Authenticate with HTTP Digest, returns true or false
|
||||||
|
def authenticate_with_http_digest(realm = "Application", &password_procedure)
|
||||||
|
HttpAuthentication::Digest.authenticate(self, realm, &password_procedure)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Render output including the HTTP Digest authentication header
|
||||||
|
def request_http_digest_authentication(realm = "Application", message = nil)
|
||||||
|
HttpAuthentication::Digest.authentication_request(self, realm, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns false on a valid response, true otherwise
|
||||||
|
def authenticate(controller, realm, &password_procedure)
|
||||||
|
authorization(controller.request) && validate_digest_response(controller.request, realm, &password_procedure)
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorization(request)
|
||||||
|
request.env['HTTP_AUTHORIZATION'] ||
|
||||||
|
request.env['X-HTTP_AUTHORIZATION'] ||
|
||||||
|
request.env['X_HTTP_AUTHORIZATION'] ||
|
||||||
|
request.env['REDIRECT_X_HTTP_AUTHORIZATION']
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns false unless the request credentials response value matches the expected value.
|
||||||
|
# First try the password as a ha1 digest password. If this fails, then try it as a plain
|
||||||
|
# text password.
|
||||||
|
def validate_digest_response(request, realm, &password_procedure)
|
||||||
|
credentials = decode_credentials_header(request)
|
||||||
|
valid_nonce = validate_nonce(request, credentials[:nonce])
|
||||||
|
|
||||||
|
if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque]
|
||||||
|
password = password_procedure.call(credentials[:username])
|
||||||
|
return false unless password
|
||||||
|
|
||||||
|
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
|
||||||
|
uri = credentials[:uri][0,1] == '/' ? request.request_uri : request.url
|
||||||
|
|
||||||
|
[true, false].any? do |password_is_ha1|
|
||||||
|
expected = expected_response(method, uri, credentials, password, password_is_ha1)
|
||||||
|
expected == credentials[:response]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
|
||||||
|
# Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
|
||||||
|
# of a plain-text password.
|
||||||
|
def expected_response(http_method, uri, credentials, password, password_is_ha1=true)
|
||||||
|
ha1 = password_is_ha1 ? password : ha1(credentials, password)
|
||||||
|
ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(':'))
|
||||||
|
::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(':'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def ha1(credentials, password)
|
||||||
|
::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_credentials(http_method, credentials, password, password_is_ha1)
|
||||||
|
credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1)
|
||||||
|
"Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
|
def decode_credentials_header(request)
|
||||||
|
decode_credentials(authorization(request))
|
||||||
|
end
|
||||||
|
|
||||||
|
def decode_credentials(header)
|
||||||
|
header.to_s.gsub(/^Digest\s+/,'').split(',').inject({}.with_indifferent_access) do |hash, pair|
|
||||||
|
key, value = pair.split('=', 2)
|
||||||
|
hash[key.strip] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')
|
||||||
|
hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def authentication_header(controller, realm)
|
||||||
|
controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def authentication_request(controller, realm, message = nil)
|
||||||
|
message ||= "HTTP Digest: Access denied.\n"
|
||||||
|
authentication_header(controller, realm)
|
||||||
|
controller.__send__ :render, :text => message, :status => :unauthorized
|
||||||
|
end
|
||||||
|
|
||||||
|
# Uses an MD5 digest based on time to generate a value to be used only once.
|
||||||
|
#
|
||||||
|
# A server-specified data string which should be uniquely generated each time a 401 response is made.
|
||||||
|
# It is recommended that this string be base64 or hexadecimal data.
|
||||||
|
# Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
|
||||||
|
#
|
||||||
|
# The contents of the nonce are implementation dependent.
|
||||||
|
# The quality of the implementation depends on a good choice.
|
||||||
|
# A nonce might, for example, be constructed as the base 64 encoding of
|
||||||
|
#
|
||||||
|
# => time-stamp H(time-stamp ":" ETag ":" private-key)
|
||||||
|
#
|
||||||
|
# where time-stamp is a server-generated time or other non-repeating value,
|
||||||
|
# ETag is the value of the HTTP ETag header associated with the requested entity,
|
||||||
|
# and private-key is data known only to the server.
|
||||||
|
# With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
|
||||||
|
# reject the request if it did not match the nonce from that header or
|
||||||
|
# if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
|
||||||
|
# The inclusion of the ETag prevents a replay request for an updated version of the resource.
|
||||||
|
# (Note: including the IP address of the client in the nonce would appear to offer the server the ability
|
||||||
|
# to limit the reuse of the nonce to the same client that originally got it.
|
||||||
|
# However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
|
||||||
|
# Also, IP address spoofing is not that hard.)
|
||||||
|
#
|
||||||
|
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
|
||||||
|
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
|
||||||
|
# POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
|
||||||
|
# of this document.
|
||||||
|
#
|
||||||
|
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
|
||||||
|
# key from the Rails session secret generated upon creation of project. Ensures
|
||||||
|
# the time cannot be modifed by client.
|
||||||
|
def nonce(time = Time.now)
|
||||||
|
t = time.to_i
|
||||||
|
hashed = [t, secret_key]
|
||||||
|
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
|
||||||
|
Base64.encode64("#{t}:#{digest}").gsub("\n", '')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Might want a shorter timeout depending on whether the request
|
||||||
|
# is a PUT or POST, and if client is browser or web service.
|
||||||
|
# Can be much shorter if the Stale directive is implemented. This would
|
||||||
|
# allow a user to use new nonce without prompting user again for their
|
||||||
|
# username and password.
|
||||||
|
def validate_nonce(request, value, seconds_to_timeout=5*60)
|
||||||
|
return false if value.nil?
|
||||||
|
t = Base64.decode64(value).split(":").first.to_i
|
||||||
|
nonce(t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
|
||||||
|
end
|
||||||
|
|
||||||
|
# Opaque based on random generation - but changing each request?
|
||||||
|
def opaque()
|
||||||
|
::Digest::MD5.hexdigest(secret_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Set in /initializers/session_store.rb, and loaded even if sessions are not in use.
|
||||||
|
def secret_key
|
||||||
|
ActionController::Base.session_options[:secret]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,35 @@
|
||||||
require 'active_support/test_case'
|
|
||||||
require 'action_controller/dispatcher'
|
|
||||||
require 'action_controller/test_process'
|
|
||||||
|
|
||||||
require 'stringio'
|
require 'stringio'
|
||||||
require 'uri'
|
require 'uri'
|
||||||
|
require 'active_support/test_case'
|
||||||
|
require 'action_controller/rack_lint_patch'
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module Integration #:nodoc:
|
module Integration #:nodoc:
|
||||||
# An integration Session instance represents a set of requests and responses
|
# An integration Session instance represents a set of requests and responses
|
||||||
# performed sequentially by some virtual user. Becase you can instantiate
|
# performed sequentially by some virtual user. Because you can instantiate
|
||||||
# multiple sessions and run them side-by-side, you can also mimic (to some
|
# multiple sessions and run them side-by-side, you can also mimic (to some
|
||||||
# limited extent) multiple simultaneous users interacting with your system.
|
# limited extent) multiple simultaneous users interacting with your system.
|
||||||
#
|
#
|
||||||
# Typically, you will instantiate a new session using IntegrationTest#open_session,
|
# Typically, you will instantiate a new session using
|
||||||
# rather than instantiating Integration::Session directly.
|
# IntegrationTest#open_session, rather than instantiating
|
||||||
|
# Integration::Session directly.
|
||||||
class Session
|
class Session
|
||||||
include Test::Unit::Assertions
|
include Test::Unit::Assertions
|
||||||
include ActionController::Assertions
|
include ActionController::TestCase::Assertions
|
||||||
include ActionController::TestProcess
|
include ActionController::TestProcess
|
||||||
|
|
||||||
|
# Rack application to use
|
||||||
|
attr_accessor :application
|
||||||
|
|
||||||
# The integer HTTP status code of the last request.
|
# The integer HTTP status code of the last request.
|
||||||
attr_reader :status
|
attr_reader :status
|
||||||
|
|
||||||
# The status message that accompanied the status code of the last request.
|
# The status message that accompanied the status code of the last request.
|
||||||
attr_reader :status_message
|
attr_reader :status_message
|
||||||
|
|
||||||
|
# The body of the last request.
|
||||||
|
attr_reader :body
|
||||||
|
|
||||||
# The URI of the last request.
|
# The URI of the last request.
|
||||||
attr_reader :path
|
attr_reader :path
|
||||||
|
|
||||||
|
|
@ -60,7 +65,8 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create and initialize a new Session instance.
|
# Create and initialize a new Session instance.
|
||||||
def initialize
|
def initialize(app = nil)
|
||||||
|
@application = app || ActionController::Dispatcher.new
|
||||||
reset!
|
reset!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -79,11 +85,13 @@ module ActionController
|
||||||
|
|
||||||
self.host = "www.example.com"
|
self.host = "www.example.com"
|
||||||
self.remote_addr = "127.0.0.1"
|
self.remote_addr = "127.0.0.1"
|
||||||
self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
|
self.accept = "text/xml,application/xml,application/xhtml+xml," +
|
||||||
|
"text/html;q=0.9,text/plain;q=0.8,image/png," +
|
||||||
|
"*/*;q=0.5"
|
||||||
|
|
||||||
unless defined? @named_routes_configured
|
unless defined? @named_routes_configured
|
||||||
# install the named routes in this session instance.
|
# install the named routes in this session instance.
|
||||||
klass = class<<self; self; end
|
klass = class << self; self; end
|
||||||
Routing::Routes.install_helpers(klass)
|
Routing::Routes.install_helpers(klass)
|
||||||
|
|
||||||
# the helpers are made protected by default--we make them public for
|
# the helpers are made protected by default--we make them public for
|
||||||
|
|
@ -97,7 +105,7 @@ module ActionController
|
||||||
#
|
#
|
||||||
# session.https!
|
# session.https!
|
||||||
# session.https!(false)
|
# session.https!(false)
|
||||||
def https!(flag=true)
|
def https!(flag = true)
|
||||||
@https = flag
|
@https = flag
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -122,7 +130,7 @@ module ActionController
|
||||||
# performed on the location header.
|
# performed on the location header.
|
||||||
def follow_redirect!
|
def follow_redirect!
|
||||||
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
|
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
|
||||||
get(interpret_uri(headers['location'].first))
|
get(interpret_uri(headers['location']))
|
||||||
status
|
status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -167,17 +175,21 @@ module ActionController
|
||||||
|
|
||||||
# Performs a GET request with the given parameters.
|
# Performs a GET request with the given parameters.
|
||||||
#
|
#
|
||||||
# - +path+: The URI (as a String) on which you want to perform a GET request.
|
# - +path+: The URI (as a String) on which you want to perform a GET
|
||||||
# - +parameters+: The HTTP parameters that you want to pass. This may be +nil+,
|
# request.
|
||||||
|
# - +parameters+: The HTTP parameters that you want to pass. This may
|
||||||
|
# be +nil+,
|
||||||
# a Hash, or a String that is appropriately encoded
|
# a Hash, or a String that is appropriately encoded
|
||||||
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
|
# (<tt>application/x-www-form-urlencoded</tt> or
|
||||||
|
# <tt>multipart/form-data</tt>).
|
||||||
# - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
|
# - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
|
||||||
# automatically be upcased, with the prefix 'HTTP_' added if needed.
|
# automatically be upcased, with the prefix 'HTTP_' added if needed.
|
||||||
#
|
#
|
||||||
# This method returns an AbstractResponse object, which one can use to inspect
|
# This method returns an Response object, which one can use to
|
||||||
# the details of the response. Furthermore, if this method was called from an
|
# inspect the details of the response. Furthermore, if this method was
|
||||||
# ActionController::IntegrationTest object, then that object's <tt>@response</tt>
|
# called from an ActionController::IntegrationTest object, then that
|
||||||
# instance variable will point to the same response object.
|
# object's <tt>@response</tt> instance variable will point to the same
|
||||||
|
# response object.
|
||||||
#
|
#
|
||||||
# You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
|
# You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
|
||||||
# +put+, +delete+, and +head+.
|
# +put+, +delete+, and +head+.
|
||||||
|
|
@ -185,22 +197,26 @@ module ActionController
|
||||||
process :get, path, parameters, headers
|
process :get, path, parameters, headers
|
||||||
end
|
end
|
||||||
|
|
||||||
# Performs a POST request with the given parameters. See get() for more details.
|
# Performs a POST request with the given parameters. See get() for more
|
||||||
|
# details.
|
||||||
def post(path, parameters = nil, headers = nil)
|
def post(path, parameters = nil, headers = nil)
|
||||||
process :post, path, parameters, headers
|
process :post, path, parameters, headers
|
||||||
end
|
end
|
||||||
|
|
||||||
# Performs a PUT request with the given parameters. See get() for more details.
|
# Performs a PUT request with the given parameters. See get() for more
|
||||||
|
# details.
|
||||||
def put(path, parameters = nil, headers = nil)
|
def put(path, parameters = nil, headers = nil)
|
||||||
process :put, path, parameters, headers
|
process :put, path, parameters, headers
|
||||||
end
|
end
|
||||||
|
|
||||||
# Performs a DELETE request with the given parameters. See get() for more details.
|
# Performs a DELETE request with the given parameters. See get() for
|
||||||
|
# more details.
|
||||||
def delete(path, parameters = nil, headers = nil)
|
def delete(path, parameters = nil, headers = nil)
|
||||||
process :delete, path, parameters, headers
|
process :delete, path, parameters, headers
|
||||||
end
|
end
|
||||||
|
|
||||||
# Performs a HEAD request with the given parameters. See get() for more details.
|
# Performs a HEAD request with the given parameters. See get() for more
|
||||||
|
# details.
|
||||||
def head(path, parameters = nil, headers = nil)
|
def head(path, parameters = nil, headers = nil)
|
||||||
process :head, path, parameters, headers
|
process :head, path, parameters, headers
|
||||||
end
|
end
|
||||||
|
|
@ -215,8 +231,7 @@ module ActionController
|
||||||
def xml_http_request(request_method, path, parameters = nil, headers = nil)
|
def xml_http_request(request_method, path, parameters = nil, headers = nil)
|
||||||
headers ||= {}
|
headers ||= {}
|
||||||
headers['X-Requested-With'] = 'XMLHttpRequest'
|
headers['X-Requested-With'] = 'XMLHttpRequest'
|
||||||
headers['Accept'] ||= 'text/javascript, text/html, application/xml, text/xml, */*'
|
headers['Accept'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
|
||||||
|
|
||||||
process(request_method, path, parameters, headers)
|
process(request_method, path, parameters, headers)
|
||||||
end
|
end
|
||||||
alias xhr :xml_http_request
|
alias xhr :xml_http_request
|
||||||
|
|
@ -224,7 +239,9 @@ module ActionController
|
||||||
# Returns the URL for the given options, according to the rules specified
|
# Returns the URL for the given options, according to the rules specified
|
||||||
# in the application's routes.
|
# in the application's routes.
|
||||||
def url_for(options)
|
def url_for(options)
|
||||||
controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
|
controller ?
|
||||||
|
controller.url_for(options) :
|
||||||
|
generic_url_rewriter.rewrite(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
@ -250,17 +267,35 @@ module ActionController
|
||||||
data = nil
|
data = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
env["QUERY_STRING"] ||= ""
|
||||||
|
|
||||||
|
data ||= ''
|
||||||
|
data.force_encoding(Encoding::ASCII_8BIT) if data.respond_to?(:force_encoding)
|
||||||
|
data = data.is_a?(IO) ? data : StringIO.new(data)
|
||||||
|
|
||||||
env.update(
|
env.update(
|
||||||
"REQUEST_METHOD" => method.to_s.upcase,
|
"REQUEST_METHOD" => method.to_s.upcase,
|
||||||
|
"SERVER_NAME" => host,
|
||||||
|
"SERVER_PORT" => (https? ? "443" : "80"),
|
||||||
|
"HTTPS" => https? ? "on" : "off",
|
||||||
|
"rack.url_scheme" => https? ? "https" : "http",
|
||||||
|
"SCRIPT_NAME" => "",
|
||||||
|
|
||||||
"REQUEST_URI" => path,
|
"REQUEST_URI" => path,
|
||||||
|
"PATH_INFO" => path,
|
||||||
"HTTP_HOST" => host,
|
"HTTP_HOST" => host,
|
||||||
"REMOTE_ADDR" => remote_addr,
|
"REMOTE_ADDR" => remote_addr,
|
||||||
"SERVER_PORT" => (https? ? "443" : "80"),
|
|
||||||
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
||||||
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
|
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
|
||||||
"HTTP_COOKIE" => encode_cookies,
|
"HTTP_COOKIE" => encode_cookies,
|
||||||
"HTTPS" => https? ? "on" : "off",
|
"HTTP_ACCEPT" => accept,
|
||||||
"HTTP_ACCEPT" => accept
|
|
||||||
|
"rack.version" => [0,1],
|
||||||
|
"rack.input" => data,
|
||||||
|
"rack.errors" => StringIO.new,
|
||||||
|
"rack.multithread" => true,
|
||||||
|
"rack.multiprocess" => true,
|
||||||
|
"rack.run_once" => false
|
||||||
)
|
)
|
||||||
|
|
||||||
(headers || {}).each do |key, value|
|
(headers || {}).each do |key, value|
|
||||||
|
|
@ -269,54 +304,62 @@ module ActionController
|
||||||
env[key] = value
|
env[key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
unless ActionController::Base.respond_to?(:clear_last_instantiation!)
|
[ControllerCapture, ActionController::ProcessWithTest].each do |mod|
|
||||||
ActionController::Base.module_eval { include ControllerCapture }
|
unless ActionController::Base < mod
|
||||||
|
ActionController::Base.class_eval { include mod }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ActionController::Base.clear_last_instantiation!
|
ActionController::Base.clear_last_instantiation!
|
||||||
|
|
||||||
env['rack.input'] = data.is_a?(IO) ? data : StringIO.new(data || '')
|
app = Rack::Lint.new(@application)
|
||||||
@status, @headers, result_body = ActionController::Dispatcher.new.mark_as_test_request!.call(env)
|
status, headers, body = app.call(env)
|
||||||
@request_count += 1
|
@request_count += 1
|
||||||
|
|
||||||
@controller = ActionController::Base.last_instantiation
|
|
||||||
@request = @controller.request
|
|
||||||
@response = @controller.response
|
|
||||||
|
|
||||||
# Decorate the response with the standard behavior of the TestResponse
|
|
||||||
# so that things like assert_response can be used in integration
|
|
||||||
# tests.
|
|
||||||
@response.extend(TestResponseBehavior)
|
|
||||||
|
|
||||||
@html_document = nil
|
@html_document = nil
|
||||||
|
|
||||||
# Inject status back in for backwords compatibility with CGI
|
@status = status.to_i
|
||||||
@headers['Status'] = @status
|
@status_message = StatusCodes::STATUS_CODES[@status]
|
||||||
|
|
||||||
@status, @status_message = @status.split(/ /)
|
@headers = Rack::Utils::HeaderHash.new(headers)
|
||||||
@status = @status.to_i
|
|
||||||
|
|
||||||
cgi_headers = Hash.new { |h,k| h[k] = [] }
|
(@headers['Set-Cookie'] || "").split("\n").each do |cookie|
|
||||||
@headers.each do |key, value|
|
|
||||||
cgi_headers[key.downcase] << value
|
|
||||||
end
|
|
||||||
cgi_headers['set-cookie'] = cgi_headers['set-cookie'].first
|
|
||||||
@headers = cgi_headers
|
|
||||||
|
|
||||||
@response.headers['cookie'] ||= []
|
|
||||||
(@headers['set-cookie'] || []).each do |cookie|
|
|
||||||
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
|
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
|
||||||
@cookies[name] = value
|
@cookies[name] = value
|
||||||
|
|
||||||
# Fake CGI cookie header
|
|
||||||
# DEPRECATE: Use response.headers["Set-Cookie"] instead
|
|
||||||
@response.headers['cookie'] << CGI::Cookie::new("name" => name, "value" => value)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return status
|
@body = ""
|
||||||
|
if body.respond_to?(:to_str)
|
||||||
|
@body << body
|
||||||
|
else
|
||||||
|
body.each { |part| @body << part }
|
||||||
|
end
|
||||||
|
|
||||||
|
if @controller = ActionController::Base.last_instantiation
|
||||||
|
@request = @controller.request
|
||||||
|
@response = @controller.response
|
||||||
|
@controller.send(:set_test_assigns)
|
||||||
|
else
|
||||||
|
# Decorate responses from Rack Middleware and Rails Metal
|
||||||
|
# as an Response for the purposes of integration testing
|
||||||
|
@response = Response.new
|
||||||
|
@response.status = status.to_s
|
||||||
|
@response.headers.replace(@headers)
|
||||||
|
@response.body = @body
|
||||||
|
end
|
||||||
|
|
||||||
|
# Decorate the response with the standard behavior of the
|
||||||
|
# TestResponse so that things like assert_response can be
|
||||||
|
# used in integration tests.
|
||||||
|
@response.extend(TestResponseBehavior)
|
||||||
|
|
||||||
|
return @status
|
||||||
rescue MultiPartNeededException
|
rescue MultiPartNeededException
|
||||||
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
|
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
|
||||||
status = process(method, path, multipart_body(parameters, boundary), (headers || {}).merge({"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
|
status = process(method, path,
|
||||||
|
multipart_body(parameters, boundary),
|
||||||
|
(headers || {}).merge(
|
||||||
|
{"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
|
||||||
return status
|
return status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -338,7 +381,7 @@ module ActionController
|
||||||
"SERVER_PORT" => https? ? "443" : "80",
|
"SERVER_PORT" => https? ? "443" : "80",
|
||||||
"HTTPS" => https? ? "on" : "off"
|
"HTTPS" => https? ? "on" : "off"
|
||||||
}
|
}
|
||||||
ActionController::UrlRewriter.new(ActionController::RackRequest.new(env), {})
|
UrlRewriter.new(Request.new(env), {})
|
||||||
end
|
end
|
||||||
|
|
||||||
def name_with_prefix(prefix, name)
|
def name_with_prefix(prefix, name)
|
||||||
|
|
@ -352,9 +395,13 @@ module ActionController
|
||||||
raise MultiPartNeededException
|
raise MultiPartNeededException
|
||||||
elsif Hash === parameters
|
elsif Hash === parameters
|
||||||
return nil if parameters.empty?
|
return nil if parameters.empty?
|
||||||
parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
|
parameters.map { |k,v|
|
||||||
|
requestify(v, name_with_prefix(prefix, k))
|
||||||
|
}.join("&")
|
||||||
elsif Array === parameters
|
elsif Array === parameters
|
||||||
parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
|
parameters.map { |v|
|
||||||
|
requestify(v, name_with_prefix(prefix, ""))
|
||||||
|
}.join("&")
|
||||||
elsif prefix.nil?
|
elsif prefix.nil?
|
||||||
parameters
|
parameters
|
||||||
else
|
else
|
||||||
|
|
@ -365,7 +412,7 @@ module ActionController
|
||||||
def multipart_requestify(params, first=true)
|
def multipart_requestify(params, first=true)
|
||||||
returning Hash.new do |p|
|
returning Hash.new do |p|
|
||||||
params.each do |key, value|
|
params.each do |key, value|
|
||||||
k = first ? CGI.escape(key.to_s) : "[#{CGI.escape(key.to_s)}]"
|
k = first ? key.to_s : "[#{key.to_s}]"
|
||||||
if Hash === value
|
if Hash === value
|
||||||
multipart_requestify(value, false).each do |subkey, subvalue|
|
multipart_requestify(value, false).each do |subkey, subvalue|
|
||||||
p[k + subkey] = subvalue
|
p[k + subkey] = subvalue
|
||||||
|
|
@ -380,7 +427,7 @@ module ActionController
|
||||||
def multipart_body(params, boundary)
|
def multipart_body(params, boundary)
|
||||||
multipart_requestify(params).map do |key, value|
|
multipart_requestify(params).map do |key, value|
|
||||||
if value.respond_to?(:original_filename)
|
if value.respond_to?(:original_filename)
|
||||||
File.open(value.path) do |f|
|
File.open(value.path, "rb") do |f|
|
||||||
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
||||||
|
|
||||||
<<-EOF
|
<<-EOF
|
||||||
|
|
@ -432,6 +479,11 @@ EOF
|
||||||
end
|
end
|
||||||
|
|
||||||
module Runner
|
module Runner
|
||||||
|
def initialize(*args)
|
||||||
|
super
|
||||||
|
@integration_session = nil
|
||||||
|
end
|
||||||
|
|
||||||
# Reset the current session. This is useful for testing multiple sessions
|
# Reset the current session. This is useful for testing multiple sessions
|
||||||
# in a single test case.
|
# in a single test case.
|
||||||
def reset!
|
def reset!
|
||||||
|
|
@ -460,8 +512,8 @@ EOF
|
||||||
# By default, a single session is automatically created for you, but you
|
# By default, a single session is automatically created for you, but you
|
||||||
# can use this method to open multiple sessions that ought to be tested
|
# can use this method to open multiple sessions that ought to be tested
|
||||||
# simultaneously.
|
# simultaneously.
|
||||||
def open_session
|
def open_session(application = nil)
|
||||||
session = Integration::Session.new
|
session = Integration::Session.new(application)
|
||||||
|
|
||||||
# delegate the fixture accessors back to the test instance
|
# delegate the fixture accessors back to the test instance
|
||||||
extras = Module.new { attr_accessor :delegate, :test_result }
|
extras = Module.new { attr_accessor :delegate, :test_result }
|
||||||
|
|
@ -469,12 +521,16 @@ EOF
|
||||||
self.class.fixture_table_names.each do |table_name|
|
self.class.fixture_table_names.each do |table_name|
|
||||||
name = table_name.tr(".", "_")
|
name = table_name.tr(".", "_")
|
||||||
next unless respond_to?(name)
|
next unless respond_to?(name)
|
||||||
extras.__send__(:define_method, name) { |*args| delegate.send(name, *args) }
|
extras.__send__(:define_method, name) { |*args|
|
||||||
|
delegate.send(name, *args)
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# delegate add_assertion to the test case
|
# delegate add_assertion to the test case
|
||||||
extras.__send__(:define_method, :add_assertion) { test_result.add_assertion }
|
extras.__send__(:define_method, :add_assertion) {
|
||||||
|
test_result.add_assertion
|
||||||
|
}
|
||||||
session.extend(extras)
|
session.extend(extras)
|
||||||
session.delegate = self
|
session.delegate = self
|
||||||
session.test_result = @_result
|
session.test_result = @_result
|
||||||
|
|
@ -495,9 +551,13 @@ EOF
|
||||||
# Delegate unhandled messages to the current session instance.
|
# Delegate unhandled messages to the current session instance.
|
||||||
def method_missing(sym, *args, &block)
|
def method_missing(sym, *args, &block)
|
||||||
reset! unless @integration_session
|
reset! unless @integration_session
|
||||||
|
if @integration_session.respond_to?(sym)
|
||||||
returning @integration_session.__send__(sym, *args, &block) do
|
returning @integration_session.__send__(sym, *args, &block) do
|
||||||
copy_session_variables!
|
copy_session_variables!
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -602,7 +662,8 @@ EOF
|
||||||
# would potentially have to set their values for both Test::Unit::TestCase
|
# would potentially have to set their values for both Test::Unit::TestCase
|
||||||
# ActionController::IntegrationTest, since by the time the value is set on
|
# ActionController::IntegrationTest, since by the time the value is set on
|
||||||
# TestCase, IntegrationTest has already been defined and cannot inherit
|
# TestCase, IntegrationTest has already been defined and cannot inherit
|
||||||
# changes to those variables. So, we make those two attributes copy-on-write.
|
# changes to those variables. So, we make those two attributes
|
||||||
|
# copy-on-write.
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def use_transactional_fixtures=(flag) #:nodoc:
|
def use_transactional_fixtures=(flag) #:nodoc:
|
||||||
|
|
|
||||||
|
|
@ -172,16 +172,8 @@ module ActionController #:nodoc:
|
||||||
@layout_conditions ||= read_inheritable_attribute(:layout_conditions)
|
@layout_conditions ||= read_inheritable_attribute(:layout_conditions)
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_layout(format) #:nodoc:
|
|
||||||
layout = read_inheritable_attribute(:layout)
|
|
||||||
return layout unless read_inheritable_attribute(:auto_layout)
|
|
||||||
@default_layout ||= {}
|
|
||||||
@default_layout[format] ||= default_layout_with_format(format, layout)
|
|
||||||
@default_layout[format]
|
|
||||||
end
|
|
||||||
|
|
||||||
def layout_list #:nodoc:
|
def layout_list #:nodoc:
|
||||||
Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] }
|
Array(view_paths).sum([]) { |path| Dir["#{path.to_str}/layouts/**/*"] }
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
@ -200,45 +192,43 @@ module ActionController #:nodoc:
|
||||||
def normalize_conditions(conditions)
|
def normalize_conditions(conditions)
|
||||||
conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
|
conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def default_layout_with_format(format, layout)
|
def initialize(*args)
|
||||||
list = layout_list
|
super
|
||||||
if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty?
|
@real_format = nil
|
||||||
(!list.grep(%r{layouts/#{layout}\.([a-z][0-9a-z]*)+$}).empty? && format == :html) ? layout : nil
|
|
||||||
else
|
|
||||||
layout
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
|
# Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
|
||||||
# is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method
|
# is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method
|
||||||
# object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
|
# object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
|
||||||
# weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
|
# weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
|
||||||
def active_layout(passed_layout = nil)
|
def active_layout(passed_layout = nil, options = {})
|
||||||
layout = passed_layout || self.class.default_layout(default_template_format)
|
layout = passed_layout || default_layout
|
||||||
|
return layout if layout.respond_to?(:render)
|
||||||
|
|
||||||
active_layout = case layout
|
active_layout = case layout
|
||||||
when String then layout
|
|
||||||
when Symbol then __send__(layout)
|
when Symbol then __send__(layout)
|
||||||
when Proc then layout.call(self)
|
when Proc then layout.call(self)
|
||||||
|
else layout
|
||||||
end
|
end
|
||||||
|
|
||||||
# Explicitly passed layout names with slashes are looked up relative to the template root,
|
find_layout(active_layout, default_template_format, options[:html_fallback]) if active_layout
|
||||||
# but auto-discovered layouts derived from a nested controller will contain a slash, though be relative
|
|
||||||
# to the 'layouts' directory so we have to check the file system to infer which case the layout name came from.
|
|
||||||
if active_layout
|
|
||||||
if active_layout.include?('/') && ! layout_directory?(active_layout)
|
|
||||||
active_layout
|
|
||||||
else
|
|
||||||
"layouts/#{active_layout}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def candidate_for_layout?(options)
|
def default_layout #:nodoc:
|
||||||
options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? &&
|
layout = self.class.read_inheritable_attribute(:layout)
|
||||||
!@template.__send__(:_exempt_from_layout?, options[:template] || default_template_name(options[:action]))
|
return layout unless self.class.read_inheritable_attribute(:auto_layout)
|
||||||
|
find_layout(layout, default_template_format)
|
||||||
|
rescue ActionView::MissingTemplate
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_layout(layout, format, html_fallback=false) #:nodoc:
|
||||||
|
view_paths.find_template(layout.to_s =~ /\A\/|layouts\// ? layout : "layouts/#{layout}", format, html_fallback)
|
||||||
|
rescue ActionView::MissingTemplate
|
||||||
|
raise if Mime::Type.lookup_by_extension(format.to_s).html?
|
||||||
end
|
end
|
||||||
|
|
||||||
def pick_layout(options)
|
def pick_layout(options)
|
||||||
|
|
@ -247,9 +237,9 @@ module ActionController #:nodoc:
|
||||||
when FalseClass
|
when FalseClass
|
||||||
nil
|
nil
|
||||||
when NilClass, TrueClass
|
when NilClass, TrueClass
|
||||||
active_layout if action_has_layout? && !@template.__send__(:_exempt_from_layout?, default_template_name)
|
active_layout if action_has_layout? && candidate_for_layout?(:template => default_template_name)
|
||||||
else
|
else
|
||||||
active_layout(layout)
|
active_layout(layout, :html_fallback => true)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
active_layout if action_has_layout? && candidate_for_layout?(options)
|
active_layout if action_has_layout? && candidate_for_layout?(options)
|
||||||
|
|
@ -271,14 +261,26 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def layout_directory?(layout_name)
|
def candidate_for_layout?(options)
|
||||||
@template.__send__(:_pick_template, "#{File.join('layouts', layout_name)}.#{@template.template_format}") ? true : false
|
template = options[:template] || default_template(options[:action])
|
||||||
|
if options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty?
|
||||||
|
begin
|
||||||
|
template_object = self.view_paths.find_template(template, default_template_format)
|
||||||
|
# this restores the behavior from 2.2.2, where response.template.template_format was reset
|
||||||
|
# to :html for :js requests with a matching html template.
|
||||||
|
# see v2.2.2, ActionView::Base, lines 328-330
|
||||||
|
@real_format = :html if response.template.template_format == :js && template_object.format == "html"
|
||||||
|
!template_object.exempt_from_layout?
|
||||||
|
rescue ActionView::MissingTemplate
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
rescue ActionView::MissingTemplate
|
rescue ActionView::MissingTemplate
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_template_format
|
def default_template_format
|
||||||
response.template.template_format
|
@real_format || response.template.template_format
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
119
vendor/rails/actionpack/lib/action_controller/middleware_stack.rb
vendored
Normal file
119
vendor/rails/actionpack/lib/action_controller/middleware_stack.rb
vendored
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
module ActionController
|
||||||
|
class MiddlewareStack < Array
|
||||||
|
class Middleware
|
||||||
|
def self.new(klass, *args, &block)
|
||||||
|
if klass.is_a?(self)
|
||||||
|
klass
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :args, :block
|
||||||
|
|
||||||
|
def initialize(klass, *args, &block)
|
||||||
|
@klass = klass
|
||||||
|
|
||||||
|
options = args.extract_options!
|
||||||
|
if options.has_key?(:if)
|
||||||
|
@conditional = options.delete(:if)
|
||||||
|
else
|
||||||
|
@conditional = true
|
||||||
|
end
|
||||||
|
args << options unless options.empty?
|
||||||
|
|
||||||
|
@args = args
|
||||||
|
@block = block
|
||||||
|
end
|
||||||
|
|
||||||
|
def klass
|
||||||
|
if @klass.respond_to?(:call)
|
||||||
|
@klass.call
|
||||||
|
elsif @klass.is_a?(Class)
|
||||||
|
@klass
|
||||||
|
else
|
||||||
|
@klass.to_s.constantize
|
||||||
|
end
|
||||||
|
rescue NameError
|
||||||
|
@klass
|
||||||
|
end
|
||||||
|
|
||||||
|
def active?
|
||||||
|
return false unless klass
|
||||||
|
|
||||||
|
if @conditional.respond_to?(:call)
|
||||||
|
@conditional.call
|
||||||
|
else
|
||||||
|
@conditional
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ==(middleware)
|
||||||
|
case middleware
|
||||||
|
when Middleware
|
||||||
|
klass == middleware.klass
|
||||||
|
when Class
|
||||||
|
klass == middleware
|
||||||
|
else
|
||||||
|
klass == middleware.to_s.constantize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
str = klass.to_s
|
||||||
|
args.each { |arg| str += ", #{arg.inspect}" }
|
||||||
|
str
|
||||||
|
end
|
||||||
|
|
||||||
|
def build(app)
|
||||||
|
if block
|
||||||
|
klass.new(app, *build_args, &block)
|
||||||
|
else
|
||||||
|
klass.new(app, *build_args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_args
|
||||||
|
Array(args).map { |arg| arg.respond_to?(:call) ? arg.call : arg }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(*args, &block)
|
||||||
|
super(*args)
|
||||||
|
block.call(self) if block_given?
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert(index, *args, &block)
|
||||||
|
index = self.index(index) unless index.is_a?(Integer)
|
||||||
|
middleware = Middleware.new(*args, &block)
|
||||||
|
super(index, middleware)
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :insert_before, :insert
|
||||||
|
|
||||||
|
def insert_after(index, *args, &block)
|
||||||
|
index = self.index(index) unless index.is_a?(Integer)
|
||||||
|
insert(index + 1, *args, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def swap(target, *args, &block)
|
||||||
|
insert_before(target, *args, &block)
|
||||||
|
delete(target)
|
||||||
|
end
|
||||||
|
|
||||||
|
def use(*args, &block)
|
||||||
|
middleware = Middleware.new(*args, &block)
|
||||||
|
push(middleware)
|
||||||
|
end
|
||||||
|
|
||||||
|
def active
|
||||||
|
find_all { |middleware| middleware.active? }
|
||||||
|
end
|
||||||
|
|
||||||
|
def build(app)
|
||||||
|
active.reverse.inject(app) { |a, e| e.build(a) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
14
vendor/rails/actionpack/lib/action_controller/middlewares.rb
vendored
Normal file
14
vendor/rails/actionpack/lib/action_controller/middlewares.rb
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
use "Rack::Lock", :if => lambda {
|
||||||
|
!ActionController::Base.allow_concurrency
|
||||||
|
}
|
||||||
|
|
||||||
|
use "ActionController::Failsafe"
|
||||||
|
|
||||||
|
use lambda { ActionController::Base.session_store },
|
||||||
|
lambda { ActionController::Base.session_options }
|
||||||
|
|
||||||
|
use "ActionController::ParamsParser"
|
||||||
|
use "Rack::MethodOverride"
|
||||||
|
use "Rack::Head"
|
||||||
|
|
||||||
|
use "ActionController::StringCoercion"
|
||||||
|
|
@ -144,11 +144,26 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def method_missing(symbol, &block)
|
def self.generate_method_for_mime(mime)
|
||||||
mime_constant = symbol.to_s.upcase
|
sym = mime.is_a?(Symbol) ? mime : mime.to_sym
|
||||||
|
const = sym.to_s.upcase
|
||||||
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
|
def #{sym}(&block) # def html(&block)
|
||||||
|
custom(Mime::#{const}, &block) # custom(Mime::HTML, &block)
|
||||||
|
end # end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
if Mime::SET.include?(Mime.const_get(mime_constant))
|
Mime::SET.each do |mime|
|
||||||
custom(Mime.const_get(mime_constant), &block)
|
generate_method_for_mime(mime)
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(symbol, &block)
|
||||||
|
mime_constant = Mime.const_get(symbol.to_s.upcase)
|
||||||
|
|
||||||
|
if Mime::SET.include?(mime_constant)
|
||||||
|
self.class.generate_method_for_mime(mime_constant)
|
||||||
|
send(symbol, &block)
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,14 @@ module Mime
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def =~(mime_type)
|
||||||
|
return false if mime_type.blank?
|
||||||
|
regexp = Regexp.new(Regexp.quote(mime_type.to_s))
|
||||||
|
(@synonyms + [ self ]).any? do |synonym|
|
||||||
|
synonym.to_s =~ regexp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
|
# Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See
|
||||||
# ActionController::RequestForgeryProtection.
|
# ActionController::RequestForgeryProtection.
|
||||||
def verify_request?
|
def verify_request?
|
||||||
|
|
|
||||||
77
vendor/rails/actionpack/lib/action_controller/params_parser.rb
vendored
Normal file
77
vendor/rails/actionpack/lib/action_controller/params_parser.rb
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
module ActionController
|
||||||
|
class ParamsParser
|
||||||
|
ActionController::Base.param_parsers[Mime::XML] = :xml_simple
|
||||||
|
ActionController::Base.param_parsers[Mime::JSON] = :json
|
||||||
|
|
||||||
|
def initialize(app)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
if params = parse_formatted_parameters(env)
|
||||||
|
env["action_controller.request.request_parameters"] = params
|
||||||
|
end
|
||||||
|
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def parse_formatted_parameters(env)
|
||||||
|
request = Request.new(env)
|
||||||
|
|
||||||
|
return false if request.content_length.zero?
|
||||||
|
|
||||||
|
mime_type = content_type_from_legacy_post_data_format_header(env) || request.content_type
|
||||||
|
strategy = ActionController::Base.param_parsers[mime_type]
|
||||||
|
|
||||||
|
return false unless strategy
|
||||||
|
|
||||||
|
case strategy
|
||||||
|
when Proc
|
||||||
|
strategy.call(request.raw_post)
|
||||||
|
when :xml_simple, :xml_node
|
||||||
|
body = request.raw_post
|
||||||
|
body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
|
||||||
|
when :yaml
|
||||||
|
YAML.load(request.raw_post)
|
||||||
|
when :json
|
||||||
|
body = request.raw_post
|
||||||
|
if body.blank?
|
||||||
|
{}
|
||||||
|
else
|
||||||
|
data = ActiveSupport::JSON.decode(body)
|
||||||
|
data = {:_json => data} unless data.is_a?(Hash)
|
||||||
|
data.with_indifferent_access
|
||||||
|
end
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
rescue Exception => e # YAML, XML or Ruby code block errors
|
||||||
|
logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
|
||||||
|
|
||||||
|
raise
|
||||||
|
{ "body" => request.raw_post,
|
||||||
|
"content_type" => request.content_type,
|
||||||
|
"content_length" => request.content_length,
|
||||||
|
"exception" => "#{e.message} (#{e.class})",
|
||||||
|
"backtrace" => e.backtrace }
|
||||||
|
end
|
||||||
|
|
||||||
|
def content_type_from_legacy_post_data_format_header(env)
|
||||||
|
if x_post_format = env['HTTP_X_POST_DATA_FORMAT']
|
||||||
|
case x_post_format.to_s.downcase
|
||||||
|
when 'yaml'
|
||||||
|
return Mime::YAML
|
||||||
|
when 'xml'
|
||||||
|
return Mime::XML
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def logger
|
||||||
|
defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
require 'action_controller/integration'
|
|
||||||
require 'active_support/testing/performance'
|
require 'active_support/testing/performance'
|
||||||
require 'active_support/testing/default'
|
require 'active_support/testing/default'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,11 @@ module ActionController
|
||||||
#
|
#
|
||||||
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
|
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
|
||||||
# * <tt>new_polymorphic_url</tt>, <tt>new_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:
|
# Example usage:
|
||||||
#
|
#
|
||||||
# edit_polymorphic_path(@post) # => "/posts/1/edit"
|
# edit_polymorphic_path(@post) # => "/posts/1/edit"
|
||||||
# formatted_polymorphic_path([@post, :pdf]) # => "/posts/1.pdf"
|
# polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
|
||||||
module PolymorphicRoutes
|
module PolymorphicRoutes
|
||||||
# Constructs a call to a named RESTful route for the given record and returns the
|
# Constructs a call to a named RESTful route for the given record and returns the
|
||||||
# resulting URL string. For example:
|
# resulting URL string. For example:
|
||||||
|
|
@ -55,7 +54,7 @@ module ActionController
|
||||||
# ==== Options
|
# ==== Options
|
||||||
#
|
#
|
||||||
# * <tt>:action</tt> - Specifies the action prefix for the named route:
|
# * <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>:new</tt> or <tt>:edit</tt>. Default is no prefix.
|
||||||
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
|
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
|
||||||
# Default is <tt>:url</tt>.
|
# Default is <tt>:url</tt>.
|
||||||
#
|
#
|
||||||
|
|
@ -78,8 +77,6 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
record = extract_record(record_or_hash_or_array)
|
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
|
args = case record_or_hash_or_array
|
||||||
when Hash; [ record_or_hash_or_array ]
|
when Hash; [ record_or_hash_or_array ]
|
||||||
|
|
@ -100,11 +97,9 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
|
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
|
||||||
args << format if format
|
named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
|
||||||
|
|
||||||
named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
|
url_options = options.except(:action, :routing_type)
|
||||||
|
|
||||||
url_options = options.except(:action, :routing_type, :format)
|
|
||||||
unless url_options.empty?
|
unless url_options.empty?
|
||||||
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
|
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
|
||||||
end
|
end
|
||||||
|
|
@ -119,28 +114,44 @@ module ActionController
|
||||||
polymorphic_url(record_or_hash_or_array, options)
|
polymorphic_url(record_or_hash_or_array, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
%w(edit new formatted).each do |action|
|
%w(edit new).each do |action|
|
||||||
module_eval <<-EOT, __FILE__, __LINE__
|
module_eval <<-EOT, __FILE__, __LINE__
|
||||||
def #{action}_polymorphic_url(record_or_hash, options = {})
|
def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
|
||||||
polymorphic_url(record_or_hash, options.merge(:action => "#{action}"))
|
polymorphic_url( # polymorphic_url(
|
||||||
|
record_or_hash, # record_or_hash,
|
||||||
|
options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
|
||||||
|
end # end
|
||||||
|
#
|
||||||
|
def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
|
||||||
|
polymorphic_url( # polymorphic_url(
|
||||||
|
record_or_hash, # record_or_hash,
|
||||||
|
options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
|
||||||
|
end # end
|
||||||
|
EOT
|
||||||
end
|
end
|
||||||
|
|
||||||
def #{action}_polymorphic_path(record_or_hash, options = {})
|
def formatted_polymorphic_url(record_or_hash, options = {})
|
||||||
polymorphic_url(record_or_hash, options.merge(:action => "#{action}", :routing_type => :path))
|
ActiveSupport::Deprecation.warn("formatted_polymorphic_url has been deprecated. Please pass :format to the polymorphic_url method instead", caller)
|
||||||
|
options[:format] = record_or_hash.pop if Array === record_or_hash
|
||||||
|
polymorphic_url(record_or_hash, options)
|
||||||
end
|
end
|
||||||
EOT
|
|
||||||
|
def formatted_polymorphic_path(record_or_hash, options = {})
|
||||||
|
ActiveSupport::Deprecation.warn("formatted_polymorphic_path has been deprecated. Please pass :format to the polymorphic_path method instead", caller)
|
||||||
|
options[:format] = record_or_hash.pop if record_or_hash === Array
|
||||||
|
polymorphic_url(record_or_hash, options.merge(:routing_type => :path))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def action_prefix(options)
|
def action_prefix(options)
|
||||||
options[:action] ? "#{options[:action]}_" : options[:format] ? "formatted_" : ""
|
options[:action] ? "#{options[:action]}_" : ''
|
||||||
end
|
end
|
||||||
|
|
||||||
def routing_type(options)
|
def routing_type(options)
|
||||||
options[:routing_type] || :url
|
options[:routing_type] || :url
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_named_route_call(records, namespace, inflection, options = {})
|
def build_named_route_call(records, inflection, options = {})
|
||||||
unless records.is_a?(Array)
|
unless records.is_a?(Array)
|
||||||
record = extract_record(records)
|
record = extract_record(records)
|
||||||
route = ''
|
route = ''
|
||||||
|
|
@ -150,7 +161,8 @@ module ActionController
|
||||||
if parent.is_a?(Symbol) || parent.is_a?(String)
|
if parent.is_a?(Symbol) || parent.is_a?(String)
|
||||||
string << "#{parent}_"
|
string << "#{parent}_"
|
||||||
else
|
else
|
||||||
string << "#{RecordIdentifier.__send__("singular_class_name", parent)}_"
|
string << RecordIdentifier.__send__("plural_class_name", parent).singularize
|
||||||
|
string << "_"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -158,10 +170,12 @@ module ActionController
|
||||||
if record.is_a?(Symbol) || record.is_a?(String)
|
if record.is_a?(Symbol) || record.is_a?(String)
|
||||||
route << "#{record}_"
|
route << "#{record}_"
|
||||||
else
|
else
|
||||||
route << "#{RecordIdentifier.__send__("#{inflection}_class_name", record)}_"
|
route << RecordIdentifier.__send__("plural_class_name", record)
|
||||||
|
route = route.singularize if inflection == :singular
|
||||||
|
route << "_"
|
||||||
end
|
end
|
||||||
|
|
||||||
action_prefix(options) + namespace + route + routing_type(options).to_s
|
action_prefix(options) + route + routing_type(options).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_record(record_or_hash_or_array)
|
def extract_record(record_or_hash_or_array)
|
||||||
|
|
@ -171,28 +185,5 @@ module ActionController
|
||||||
else record_or_hash_or_array
|
else record_or_hash_or_array
|
||||||
end
|
end
|
||||||
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
|
|
||||||
|
|
||||||
# Remove the first symbols from the array and return the url prefix
|
|
||||||
# implied by those symbols.
|
|
||||||
def extract_namespace(record_or_hash_or_array)
|
|
||||||
return "" unless record_or_hash_or_array.is_a?(Array)
|
|
||||||
|
|
||||||
namespace_keys = []
|
|
||||||
while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol)
|
|
||||||
namespace_keys << record_or_hash_or_array.shift
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace_keys.map {|k| "#{k}_"}.join
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
36
vendor/rails/actionpack/lib/action_controller/rack_lint_patch.rb
vendored
Normal file
36
vendor/rails/actionpack/lib/action_controller/rack_lint_patch.rb
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Rack 1.0 does not allow string subclass body. This does not play well with our ActionView::SafeBuffer.
|
||||||
|
# The next release of Rack will be allowing string subclass body - http://github.com/rack/rack/commit/de668df02802a0335376a81ba709270e43ba9d55
|
||||||
|
# TODO : Remove this monkey patch after the next release of Rack
|
||||||
|
|
||||||
|
module RackLintPatch
|
||||||
|
module AllowStringSubclass
|
||||||
|
def self.included(base)
|
||||||
|
base.send :alias_method, :each, :each_with_hack
|
||||||
|
end
|
||||||
|
|
||||||
|
def each_with_hack
|
||||||
|
@closed = false
|
||||||
|
|
||||||
|
@body.each { |part|
|
||||||
|
assert("Body yielded non-string value #{part.inspect}") {
|
||||||
|
part.kind_of?(String)
|
||||||
|
}
|
||||||
|
yield part
|
||||||
|
}
|
||||||
|
|
||||||
|
if @body.respond_to?(:to_path)
|
||||||
|
assert("The file identified by body.to_path does not exist") {
|
||||||
|
::File.exist? @body.to_path
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
app = proc {|env| [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, [Class.new(String).new("Hello World!")]] }
|
||||||
|
response = Rack::MockRequest.new(Rack::Lint.new(app)).get('/')
|
||||||
|
rescue Rack::Lint::LintError => e
|
||||||
|
raise(e) unless e.message =~ /Body yielded non-string value/
|
||||||
|
Rack::Lint.send :include, AllowStringSubclass
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,303 +0,0 @@
|
||||||
require 'action_controller/cgi_ext'
|
|
||||||
require 'action_controller/session/cookie_store'
|
|
||||||
|
|
||||||
module ActionController #:nodoc:
|
|
||||||
class RackRequest < AbstractRequest #:nodoc:
|
|
||||||
attr_accessor :session_options
|
|
||||||
attr_reader :cgi
|
|
||||||
|
|
||||||
class SessionFixationAttempt < StandardError #:nodoc:
|
|
||||||
end
|
|
||||||
|
|
||||||
DEFAULT_SESSION_OPTIONS = {
|
|
||||||
:database_manager => CGI::Session::CookieStore, # store data in cookie
|
|
||||||
:prefix => "ruby_sess.", # prefix session file names
|
|
||||||
:session_path => "/", # available to all paths in app
|
|
||||||
:session_key => "_session_id",
|
|
||||||
:cookie_only => true,
|
|
||||||
:session_http_only=> true
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
|
|
||||||
@session_options = session_options
|
|
||||||
@env = env
|
|
||||||
@cgi = CGIWrapper.new(self)
|
|
||||||
super()
|
|
||||||
end
|
|
||||||
|
|
||||||
%w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO
|
|
||||||
PATH_TRANSLATED REMOTE_HOST
|
|
||||||
REMOTE_IDENT REMOTE_USER SCRIPT_NAME
|
|
||||||
SERVER_NAME SERVER_PROTOCOL
|
|
||||||
|
|
||||||
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
|
|
||||||
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
|
|
||||||
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
|
|
||||||
define_method(env.sub(/^HTTP_/n, '').downcase) do
|
|
||||||
@env[env]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def query_string
|
|
||||||
qs = super
|
|
||||||
if !qs.blank?
|
|
||||||
qs
|
|
||||||
else
|
|
||||||
@env['QUERY_STRING']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def body_stream #:nodoc:
|
|
||||||
@env['rack.input']
|
|
||||||
end
|
|
||||||
|
|
||||||
def key?(key)
|
|
||||||
@env.key?(key)
|
|
||||||
end
|
|
||||||
|
|
||||||
def cookies
|
|
||||||
return {} unless @env["HTTP_COOKIE"]
|
|
||||||
|
|
||||||
unless @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
|
|
||||||
@env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
|
|
||||||
@env["rack.request.cookie_hash"] = CGI::Cookie::parse(@env["rack.request.cookie_string"])
|
|
||||||
end
|
|
||||||
|
|
||||||
@env["rack.request.cookie_hash"]
|
|
||||||
end
|
|
||||||
|
|
||||||
def server_port
|
|
||||||
@env['SERVER_PORT'].to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
def server_software
|
|
||||||
@env['SERVER_SOFTWARE'].split("/").first
|
|
||||||
end
|
|
||||||
|
|
||||||
def session
|
|
||||||
unless defined?(@session)
|
|
||||||
if @session_options == false
|
|
||||||
@session = Hash.new
|
|
||||||
else
|
|
||||||
stale_session_check! do
|
|
||||||
if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
|
|
||||||
raise SessionFixationAttempt
|
|
||||||
end
|
|
||||||
case value = session_options_with_string_keys['new_session']
|
|
||||||
when true
|
|
||||||
@session = new_session
|
|
||||||
when false
|
|
||||||
begin
|
|
||||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
|
||||||
# CGI::Session raises ArgumentError if 'new_session' == false
|
|
||||||
# and no session cookie or query param is present.
|
|
||||||
rescue ArgumentError
|
|
||||||
@session = Hash.new
|
|
||||||
end
|
|
||||||
when nil
|
|
||||||
@session = CGI::Session.new(@cgi, session_options_with_string_keys)
|
|
||||||
else
|
|
||||||
raise ArgumentError, "Invalid new_session option: #{value}"
|
|
||||||
end
|
|
||||||
@session['__valid_session']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@session
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset_session
|
|
||||||
@session.delete if defined?(@session) && @session.is_a?(CGI::Session)
|
|
||||||
@session = new_session
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
# Delete an old session if it exists then create a new one.
|
|
||||||
def new_session
|
|
||||||
if @session_options == false
|
|
||||||
Hash.new
|
|
||||||
else
|
|
||||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
|
|
||||||
CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def cookie_only?
|
|
||||||
session_options_with_string_keys['cookie_only']
|
|
||||||
end
|
|
||||||
|
|
||||||
def stale_session_check!
|
|
||||||
yield
|
|
||||||
rescue ArgumentError => argument_error
|
|
||||||
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
|
||||||
begin
|
|
||||||
# Note that the regexp does not allow $1 to end with a ':'
|
|
||||||
$1.constantize
|
|
||||||
rescue LoadError, NameError => const_error
|
|
||||||
raise ActionController::SessionRestoreError, <<-end_msg
|
|
||||||
Session contains objects whose class definition isn\'t available.
|
|
||||||
Remember to require the classes for all objects kept in the session.
|
|
||||||
(Original exception: #{const_error.message} [#{const_error.class}])
|
|
||||||
end_msg
|
|
||||||
end
|
|
||||||
|
|
||||||
retry
|
|
||||||
else
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def session_options_with_string_keys
|
|
||||||
@session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class RackResponse < AbstractResponse #:nodoc:
|
|
||||||
def initialize(request)
|
|
||||||
@cgi = request.cgi
|
|
||||||
@writer = lambda { |x| @body << x }
|
|
||||||
@block = nil
|
|
||||||
super()
|
|
||||||
end
|
|
||||||
|
|
||||||
# Retrieve status from instance variable if has already been delete
|
|
||||||
def status
|
|
||||||
@status || super
|
|
||||||
end
|
|
||||||
|
|
||||||
def out(output = $stdout, &block)
|
|
||||||
# Nasty hack because CGI sessions are closed after the normal
|
|
||||||
# prepare! statement
|
|
||||||
set_cookies!
|
|
||||||
|
|
||||||
@block = block
|
|
||||||
@status = headers.delete("Status")
|
|
||||||
if [204, 304].include?(status.to_i)
|
|
||||||
headers.delete("Content-Type")
|
|
||||||
[status, headers.to_hash, []]
|
|
||||||
else
|
|
||||||
[status, headers.to_hash, self]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
alias to_a out
|
|
||||||
|
|
||||||
def each(&callback)
|
|
||||||
if @body.respond_to?(:call)
|
|
||||||
@writer = lambda { |x| callback.call(x) }
|
|
||||||
@body.call(self, self)
|
|
||||||
elsif @body.is_a?(String)
|
|
||||||
@body.each_line(&callback)
|
|
||||||
else
|
|
||||||
@body.each(&callback)
|
|
||||||
end
|
|
||||||
|
|
||||||
@writer = callback
|
|
||||||
@block.call(self) if @block
|
|
||||||
end
|
|
||||||
|
|
||||||
def write(str)
|
|
||||||
@writer.call str.to_s
|
|
||||||
str
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
@body.close if @body.respond_to?(:close)
|
|
||||||
end
|
|
||||||
|
|
||||||
def empty?
|
|
||||||
@block == nil && @body.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def prepare!
|
|
||||||
super
|
|
||||||
|
|
||||||
convert_language!
|
|
||||||
convert_expires!
|
|
||||||
set_status!
|
|
||||||
# set_cookies!
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def convert_language!
|
|
||||||
headers["Content-Language"] = headers.delete("language") if headers["language"]
|
|
||||||
end
|
|
||||||
|
|
||||||
def convert_expires!
|
|
||||||
headers["Expires"] = headers.delete("") if headers["expires"]
|
|
||||||
end
|
|
||||||
|
|
||||||
def convert_content_type!
|
|
||||||
super
|
|
||||||
headers['Content-Type'] = headers.delete('type') || "text/html"
|
|
||||||
headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset']
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_content_length!
|
|
||||||
super
|
|
||||||
headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"]
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_status!
|
|
||||||
self.status ||= "200 OK"
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_cookies!
|
|
||||||
# Convert 'cookie' header to 'Set-Cookie' headers.
|
|
||||||
# Because Set-Cookie header can appear more the once in the response body,
|
|
||||||
# we store it in a line break separated string that will be translated to
|
|
||||||
# multiple Set-Cookie header by the handler.
|
|
||||||
if cookie = headers.delete('cookie')
|
|
||||||
cookies = []
|
|
||||||
|
|
||||||
case cookie
|
|
||||||
when Array then cookie.each { |c| cookies << c.to_s }
|
|
||||||
when Hash then cookie.each { |_, c| cookies << c.to_s }
|
|
||||||
else cookies << cookie.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
@cgi.output_cookies.each { |c| cookies << c.to_s } if @cgi.output_cookies
|
|
||||||
|
|
||||||
headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class CGIWrapper < ::CGI
|
|
||||||
attr_reader :output_cookies
|
|
||||||
|
|
||||||
def initialize(request, *args)
|
|
||||||
@request = request
|
|
||||||
@args = *args
|
|
||||||
@input = request.body
|
|
||||||
|
|
||||||
super *args
|
|
||||||
end
|
|
||||||
|
|
||||||
def params
|
|
||||||
@params ||= @request.params
|
|
||||||
end
|
|
||||||
|
|
||||||
def cookies
|
|
||||||
@request.cookies
|
|
||||||
end
|
|
||||||
|
|
||||||
def query_string
|
|
||||||
@request.query_string
|
|
||||||
end
|
|
||||||
|
|
||||||
# Used to wrap the normal args variable used inside CGI.
|
|
||||||
def args
|
|
||||||
@args
|
|
||||||
end
|
|
||||||
|
|
||||||
# Used to wrap the normal env_table variable used inside CGI.
|
|
||||||
def env_table
|
|
||||||
@request.env
|
|
||||||
end
|
|
||||||
|
|
||||||
# Used to wrap the normal stdinput variable used inside CGI.
|
|
||||||
def stdinput
|
|
||||||
@input
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
54
vendor/rails/actionpack/lib/action_controller/reloader.rb
vendored
Normal file
54
vendor/rails/actionpack/lib/action_controller/reloader.rb
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
require 'thread'
|
||||||
|
|
||||||
|
module ActionController
|
||||||
|
class Reloader
|
||||||
|
@@default_lock = Mutex.new
|
||||||
|
cattr_accessor :default_lock
|
||||||
|
|
||||||
|
class BodyWrapper
|
||||||
|
def initialize(body, lock)
|
||||||
|
@body = body
|
||||||
|
@lock = lock
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
@body.close if @body.respond_to?(:close)
|
||||||
|
ensure
|
||||||
|
Dispatcher.cleanup_application
|
||||||
|
@lock.unlock
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(*args, &block)
|
||||||
|
@body.send(*args, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_to?(symbol, include_private = false)
|
||||||
|
symbol == :close || @body.respond_to?(symbol, include_private)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.run(lock = @@default_lock)
|
||||||
|
lock.lock
|
||||||
|
begin
|
||||||
|
Dispatcher.reload_application
|
||||||
|
status, headers, body = yield
|
||||||
|
# We do not want to call 'cleanup_application' in an ensure block
|
||||||
|
# because the returned Rack response body may lazily generate its data. This
|
||||||
|
# is for example the case if one calls
|
||||||
|
#
|
||||||
|
# render :text => lambda { ... code here which refers to application models ... }
|
||||||
|
#
|
||||||
|
# in an ActionController.
|
||||||
|
#
|
||||||
|
# Instead, we will want to cleanup the application code after the request is
|
||||||
|
# completely finished. So we wrap the body in a BodyWrapper class so that
|
||||||
|
# when the Rack handler calls #close during the end of the request, we get to
|
||||||
|
# run our cleanup code.
|
||||||
|
[status, headers, BodyWrapper.new(body, lock)]
|
||||||
|
rescue Exception
|
||||||
|
lock.unlock
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -3,39 +3,42 @@ require 'stringio'
|
||||||
require 'strscan'
|
require 'strscan'
|
||||||
|
|
||||||
require 'active_support/memoizable'
|
require 'active_support/memoizable'
|
||||||
|
require 'action_controller/cgi_ext'
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# CgiRequest and TestRequest provide concrete implementations.
|
class Request < Rack::Request
|
||||||
class AbstractRequest
|
|
||||||
extend ActiveSupport::Memoizable
|
|
||||||
|
|
||||||
def self.relative_url_root=(relative_url_root)
|
%w[ AUTH_TYPE GATEWAY_INTERFACE
|
||||||
ActiveSupport::Deprecation.warn(
|
PATH_TRANSLATED REMOTE_HOST
|
||||||
"ActionController::AbstractRequest.relative_url_root= has been renamed." +
|
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
|
||||||
"You can now set it with config.action_controller.relative_url_root=", caller)
|
SERVER_NAME SERVER_PROTOCOL
|
||||||
ActionController::Base.relative_url_root=relative_url_root
|
|
||||||
|
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
|
||||||
|
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
|
||||||
|
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
|
||||||
|
define_method(env.sub(/^HTTP_/n, '').downcase) do
|
||||||
|
@env[env]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def key?(key)
|
||||||
|
@env.key?(key)
|
||||||
end
|
end
|
||||||
|
|
||||||
HTTP_METHODS = %w(get head put post delete options)
|
HTTP_METHODS = %w(get head put post delete options)
|
||||||
HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
|
HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
|
||||||
|
|
||||||
# The hash of environment variables for this request,
|
# Returns the true HTTP request \method as a lowercase symbol, such as
|
||||||
# such as { 'RAILS_ENV' => 'production' }.
|
# <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
|
||||||
attr_reader :env
|
# constant above, an UnknownHttpMethod exception is raised.
|
||||||
|
|
||||||
# The true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
|
|
||||||
# UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
|
|
||||||
def request_method
|
def request_method
|
||||||
method = @env['REQUEST_METHOD']
|
@request_method ||= HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
|
||||||
method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank?
|
|
||||||
|
|
||||||
HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
|
|
||||||
end
|
end
|
||||||
memoize :request_method
|
|
||||||
|
|
||||||
# The HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
|
# Returns the HTTP request \method used for action processing as a
|
||||||
# Note, HEAD is returned as <tt>:get</tt> since the two are functionally
|
# lowercase symbol, such as <tt>:post</tt>. (Unlike #request_method, this
|
||||||
# equivalent from the application's perspective.
|
# method returns <tt>:get</tt> for a HEAD request because the two are
|
||||||
|
# functionally equivalent from the application's perspective.)
|
||||||
def method
|
def method
|
||||||
request_method == :head ? :get : request_method
|
request_method == :head ? :get : request_method
|
||||||
end
|
end
|
||||||
|
|
@ -70,27 +73,35 @@ module ActionController
|
||||||
#
|
#
|
||||||
# request.headers["Content-Type"] # => "text/plain"
|
# request.headers["Content-Type"] # => "text/plain"
|
||||||
def headers
|
def headers
|
||||||
ActionController::Http::Headers.new(@env)
|
@headers ||= ActionController::Http::Headers.new(@env)
|
||||||
end
|
end
|
||||||
memoize :headers
|
|
||||||
|
|
||||||
# Returns the content length of the request as an integer.
|
# Returns the content length of the request as an integer.
|
||||||
def content_length
|
def content_length
|
||||||
@env['CONTENT_LENGTH'].to_i
|
super.to_i
|
||||||
end
|
end
|
||||||
memoize :content_length
|
|
||||||
|
|
||||||
# The MIME type of the HTTP request, such as Mime::XML.
|
# The MIME type of the HTTP request, such as Mime::XML.
|
||||||
#
|
#
|
||||||
# For backward compatibility, the post \format is extracted from the
|
# For backward compatibility, the post \format is extracted from the
|
||||||
# X-Post-Data-Format HTTP header if present.
|
# X-Post-Data-Format HTTP header if present.
|
||||||
def content_type
|
def content_type
|
||||||
Mime::Type.lookup(content_type_without_parameters)
|
@content_type ||= begin
|
||||||
|
if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
|
||||||
|
Mime::Type.lookup($1.strip.downcase)
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def media_type
|
||||||
|
content_type.to_s
|
||||||
end
|
end
|
||||||
memoize :content_type
|
|
||||||
|
|
||||||
# Returns the accepted MIME type for the request.
|
# Returns the accepted MIME type for the request.
|
||||||
def accepts
|
def accepts
|
||||||
|
@accepts ||= begin
|
||||||
header = @env['HTTP_ACCEPT'].to_s.strip
|
header = @env['HTTP_ACCEPT'].to_s.strip
|
||||||
|
|
||||||
if header.empty?
|
if header.empty?
|
||||||
|
|
@ -99,14 +110,13 @@ module ActionController
|
||||||
Mime::Type.parse(header)
|
Mime::Type.parse(header)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
memoize :accepts
|
end
|
||||||
|
|
||||||
def if_modified_since
|
def if_modified_since
|
||||||
if since = env['HTTP_IF_MODIFIED_SINCE']
|
if since = env['HTTP_IF_MODIFIED_SINCE']
|
||||||
Time.rfc2822(since) rescue nil
|
Time.rfc2822(since) rescue nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
memoize :if_modified_since
|
|
||||||
|
|
||||||
def if_none_match
|
def if_none_match
|
||||||
env['HTTP_IF_NONE_MATCH']
|
env['HTTP_IF_NONE_MATCH']
|
||||||
|
|
@ -209,7 +219,7 @@ module ActionController
|
||||||
# delimited list in the case of multiple chained proxies; the last
|
# delimited list in the case of multiple chained proxies; the last
|
||||||
# address which is not trusted is the originating IP.
|
# address which is not trusted is the originating IP.
|
||||||
def remote_ip
|
def remote_ip
|
||||||
remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].split(',').collect(&:strip)
|
remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/)
|
||||||
|
|
||||||
unless remote_addr_list.blank?
|
unless remote_addr_list.blank?
|
||||||
not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
|
not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
|
||||||
|
|
@ -218,7 +228,7 @@ module ActionController
|
||||||
remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
|
remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
|
||||||
|
|
||||||
if @env.include? 'HTTP_CLIENT_IP'
|
if @env.include? 'HTTP_CLIENT_IP'
|
||||||
if remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
|
if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
|
||||||
# We don't know which came from the proxy, and which from the user
|
# We don't know which came from the proxy, and which from the user
|
||||||
raise ActionControllerError.new(<<EOM)
|
raise ActionControllerError.new(<<EOM)
|
||||||
IP spoofing attack?!
|
IP spoofing attack?!
|
||||||
|
|
@ -240,26 +250,21 @@ EOM
|
||||||
|
|
||||||
@env['REMOTE_ADDR']
|
@env['REMOTE_ADDR']
|
||||||
end
|
end
|
||||||
memoize :remote_ip
|
|
||||||
|
|
||||||
# Returns the lowercase name of the HTTP server software.
|
# Returns the lowercase name of the HTTP server software.
|
||||||
def server_software
|
def server_software
|
||||||
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
|
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
|
||||||
end
|
end
|
||||||
memoize :server_software
|
|
||||||
|
|
||||||
|
|
||||||
# Returns the complete URL used for this request.
|
# Returns the complete URL used for this request.
|
||||||
def url
|
def url
|
||||||
protocol + host_with_port + request_uri
|
protocol + host_with_port + request_uri
|
||||||
end
|
end
|
||||||
memoize :url
|
|
||||||
|
|
||||||
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
|
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
|
||||||
def protocol
|
def protocol
|
||||||
ssl? ? 'https://' : 'http://'
|
ssl? ? 'https://' : 'http://'
|
||||||
end
|
end
|
||||||
memoize :protocol
|
|
||||||
|
|
||||||
# Is this an SSL request?
|
# Is this an SSL request?
|
||||||
def ssl?
|
def ssl?
|
||||||
|
|
@ -271,7 +276,7 @@ EOM
|
||||||
if forwarded = env["HTTP_X_FORWARDED_HOST"]
|
if forwarded = env["HTTP_X_FORWARDED_HOST"]
|
||||||
forwarded.split(/,\s?/).last
|
forwarded.split(/,\s?/).last
|
||||||
else
|
else
|
||||||
env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
|
env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -279,14 +284,12 @@ EOM
|
||||||
def host
|
def host
|
||||||
raw_host_with_port.sub(/:\d+$/, '')
|
raw_host_with_port.sub(/:\d+$/, '')
|
||||||
end
|
end
|
||||||
memoize :host
|
|
||||||
|
|
||||||
# Returns a \host:\port string for this request, such as "example.com" or
|
# Returns a \host:\port string for this request, such as "example.com" or
|
||||||
# "example.com:8080".
|
# "example.com:8080".
|
||||||
def host_with_port
|
def host_with_port
|
||||||
"#{host}#{port_string}"
|
"#{host}#{port_string}"
|
||||||
end
|
end
|
||||||
memoize :host_with_port
|
|
||||||
|
|
||||||
# Returns the port number of this request as an integer.
|
# Returns the port number of this request as an integer.
|
||||||
def port
|
def port
|
||||||
|
|
@ -296,7 +299,6 @@ EOM
|
||||||
standard_port
|
standard_port
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
memoize :port
|
|
||||||
|
|
||||||
# Returns the standard \port number for this request's protocol.
|
# Returns the standard \port number for this request's protocol.
|
||||||
def standard_port
|
def standard_port
|
||||||
|
|
@ -332,13 +334,8 @@ EOM
|
||||||
|
|
||||||
# Returns the query string, accounting for server idiosyncrasies.
|
# Returns the query string, accounting for server idiosyncrasies.
|
||||||
def query_string
|
def query_string
|
||||||
if uri = @env['REQUEST_URI']
|
@env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
|
||||||
uri.split('?', 2)[1] || ''
|
|
||||||
else
|
|
||||||
@env['QUERY_STRING'] || ''
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
memoize :query_string
|
|
||||||
|
|
||||||
# Returns the request URI, accounting for server idiosyncrasies.
|
# Returns the request URI, accounting for server idiosyncrasies.
|
||||||
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
|
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
|
||||||
|
|
@ -364,36 +361,33 @@ EOM
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
memoize :request_uri
|
|
||||||
|
|
||||||
# Returns the interpreted \path to requested resource after all the installation
|
# Returns the interpreted \path to requested resource after all the installation
|
||||||
# directory of this application was taken into account.
|
# directory of this application was taken into account.
|
||||||
def path
|
def path
|
||||||
path = (uri = request_uri) ? uri.split('?').first.to_s : ''
|
path = request_uri.to_s[/\A[^\?]*/]
|
||||||
|
path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
|
||||||
# Cut off the path to the installation directory if given
|
path
|
||||||
path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '')
|
|
||||||
path || ''
|
|
||||||
end
|
end
|
||||||
memoize :path
|
|
||||||
|
|
||||||
# Read the request \body. This is useful for web services that need to
|
# Read the request \body. This is useful for web services that need to
|
||||||
# work with raw requests directly.
|
# work with raw requests directly.
|
||||||
def raw_post
|
def raw_post
|
||||||
unless env.include? 'RAW_POST_DATA'
|
unless @env.include? 'RAW_POST_DATA'
|
||||||
env['RAW_POST_DATA'] = body.read(content_length)
|
@env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
|
||||||
body.rewind if body.respond_to?(:rewind)
|
body.rewind if body.respond_to?(:rewind)
|
||||||
end
|
end
|
||||||
env['RAW_POST_DATA']
|
@env['RAW_POST_DATA']
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns both GET and POST \parameters in a single hash.
|
# Returns both GET and POST \parameters in a single hash.
|
||||||
def parameters
|
def parameters
|
||||||
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
|
@parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
|
||||||
end
|
end
|
||||||
|
alias_method :params, :parameters
|
||||||
|
|
||||||
def path_parameters=(parameters) #:nodoc:
|
def path_parameters=(parameters) #:nodoc:
|
||||||
@path_parameters = parameters
|
@env["action_controller.request.path_parameters"] = parameters
|
||||||
@symbolized_path_parameters = @parameters = nil
|
@symbolized_path_parameters = @parameters = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -409,464 +403,91 @@ EOM
|
||||||
#
|
#
|
||||||
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
|
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
|
||||||
def path_parameters
|
def path_parameters
|
||||||
@path_parameters ||= {}
|
@env["action_controller.request.path_parameters"] ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# The request body is an IO input stream. If the RAW_POST_DATA environment
|
# The request body is an IO input stream. If the RAW_POST_DATA environment
|
||||||
# variable is already set, wrap it in a StringIO.
|
# variable is already set, wrap it in a StringIO.
|
||||||
def body
|
def body
|
||||||
if raw_post = env['RAW_POST_DATA']
|
if raw_post = @env['RAW_POST_DATA']
|
||||||
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
|
raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
|
||||||
StringIO.new(raw_post)
|
StringIO.new(raw_post)
|
||||||
else
|
else
|
||||||
body_stream
|
@env['rack.input']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_addr
|
def form_data?
|
||||||
@env['REMOTE_ADDR']
|
FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
def referrer
|
# Override Rack's GET method to support indifferent access
|
||||||
@env['HTTP_REFERER']
|
def GET
|
||||||
|
@env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
|
||||||
end
|
end
|
||||||
alias referer referrer
|
alias_method :query_parameters, :GET
|
||||||
|
|
||||||
|
# Override Rack's POST method to support indifferent access
|
||||||
def query_parameters
|
def POST
|
||||||
@query_parameters ||= self.class.parse_query_parameters(query_string)
|
@env["action_controller.request.request_parameters"] ||= normalize_parameters(super)
|
||||||
end
|
end
|
||||||
|
alias_method :request_parameters, :POST
|
||||||
def request_parameters
|
|
||||||
@request_parameters ||= parse_formatted_request_parameters
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
#--
|
|
||||||
# Must be implemented in the concrete request
|
|
||||||
#++
|
|
||||||
|
|
||||||
def body_stream #:nodoc:
|
def body_stream #:nodoc:
|
||||||
|
@env['rack.input']
|
||||||
end
|
end
|
||||||
|
|
||||||
def cookies #:nodoc:
|
def session
|
||||||
end
|
@env['rack.session'] ||= {}
|
||||||
|
|
||||||
def session #:nodoc:
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def session=(session) #:nodoc:
|
def session=(session) #:nodoc:
|
||||||
@session = session
|
@env['rack.session'] = session
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_session #:nodoc:
|
def reset_session
|
||||||
|
@env['rack.session.options'].delete(:id)
|
||||||
|
@env['rack.session'] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
def session_options
|
||||||
# The raw content type string. Use when you need parameters such as
|
@env['rack.session.options'] ||= {}
|
||||||
# charset or boundary which aren't included in the content_type MIME type.
|
|
||||||
# Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
|
|
||||||
def content_type_with_parameters
|
|
||||||
content_type_from_legacy_post_data_format_header ||
|
|
||||||
env['CONTENT_TYPE'].to_s
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# The raw content type string with its parameters stripped off.
|
def session_options=(options)
|
||||||
def content_type_without_parameters
|
@env['rack.session.options'] = options
|
||||||
self.class.extract_content_type_without_parameters(content_type_with_parameters)
|
end
|
||||||
|
|
||||||
|
def server_port
|
||||||
|
@env['SERVER_PORT'].to_i
|
||||||
end
|
end
|
||||||
memoize :content_type_without_parameters
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def content_type_from_legacy_post_data_format_header
|
|
||||||
if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
|
|
||||||
case x_post_format.to_s.downcase
|
|
||||||
when 'yaml'; 'application/x-yaml'
|
|
||||||
when 'xml'; 'application/xml'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_formatted_request_parameters
|
|
||||||
return {} if content_length.zero?
|
|
||||||
|
|
||||||
content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
|
|
||||||
|
|
||||||
# Don't parse params for unknown requests.
|
|
||||||
return {} if content_type.blank?
|
|
||||||
|
|
||||||
mime_type = Mime::Type.lookup(content_type)
|
|
||||||
strategy = ActionController::Base.param_parsers[mime_type]
|
|
||||||
|
|
||||||
# Only multipart form parsing expects a stream.
|
|
||||||
body = (strategy && strategy != :multipart_form) ? raw_post : self.body
|
|
||||||
|
|
||||||
case strategy
|
|
||||||
when Proc
|
|
||||||
strategy.call(body)
|
|
||||||
when :url_encoded_form
|
|
||||||
self.class.clean_up_ajax_request_body! body
|
|
||||||
self.class.parse_query_parameters(body)
|
|
||||||
when :multipart_form
|
|
||||||
self.class.parse_multipart_form_parameters(body, boundary, content_length, env)
|
|
||||||
when :xml_simple, :xml_node
|
|
||||||
body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
|
|
||||||
when :yaml
|
|
||||||
YAML.load(body)
|
|
||||||
when :json
|
|
||||||
if body.blank?
|
|
||||||
{}
|
|
||||||
else
|
|
||||||
data = ActiveSupport::JSON.decode(body)
|
|
||||||
data = {:_json => data} unless data.is_a?(Hash)
|
|
||||||
data.with_indifferent_access
|
|
||||||
end
|
|
||||||
else
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
rescue Exception => e # YAML, XML or Ruby code block errors
|
|
||||||
raise
|
|
||||||
{ "body" => body,
|
|
||||||
"content_type" => content_type_with_parameters,
|
|
||||||
"content_length" => content_length,
|
|
||||||
"exception" => "#{e.message} (#{e.class})",
|
|
||||||
"backtrace" => e.backtrace }
|
|
||||||
end
|
|
||||||
|
|
||||||
def named_host?(host)
|
def named_host?(host)
|
||||||
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
|
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
|
||||||
end
|
end
|
||||||
|
|
||||||
class << self
|
# Convert nested Hashs to HashWithIndifferentAccess and replace
|
||||||
def parse_query_parameters(query_string)
|
# file upload hashs with UploadedFile objects
|
||||||
return {} if query_string.blank?
|
def normalize_parameters(value)
|
||||||
|
|
||||||
pairs = query_string.split('&').collect do |chunk|
|
|
||||||
next if chunk.empty?
|
|
||||||
key, value = chunk.split('=', 2)
|
|
||||||
next if key.empty?
|
|
||||||
value = value.nil? ? nil : CGI.unescape(value)
|
|
||||||
[ CGI.unescape(key), value ]
|
|
||||||
end.compact
|
|
||||||
|
|
||||||
UrlEncodedPairParser.new(pairs).result
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_request_parameters(params)
|
|
||||||
parser = UrlEncodedPairParser.new
|
|
||||||
|
|
||||||
params = params.dup
|
|
||||||
until params.empty?
|
|
||||||
for key, value in params
|
|
||||||
if key.blank?
|
|
||||||
params.delete key
|
|
||||||
elsif !key.include?('[')
|
|
||||||
# much faster to test for the most common case first (GET)
|
|
||||||
# and avoid the call to build_deep_hash
|
|
||||||
parser.result[key] = get_typed_value(value[0])
|
|
||||||
params.delete key
|
|
||||||
elsif value.is_a?(Array)
|
|
||||||
parser.parse(key, get_typed_value(value.shift))
|
|
||||||
params.delete key if value.empty?
|
|
||||||
else
|
|
||||||
raise TypeError, "Expected array, found #{value.inspect}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.result
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_multipart_form_parameters(body, boundary, body_size, env)
|
|
||||||
parse_request_parameters(read_multipart(body, boundary, body_size, env))
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_multipart_boundary(content_type_with_parameters)
|
|
||||||
if content_type_with_parameters =~ MULTIPART_BOUNDARY
|
|
||||||
['multipart/form-data', $1.dup]
|
|
||||||
else
|
|
||||||
extract_content_type_without_parameters(content_type_with_parameters)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_content_type_without_parameters(content_type_with_parameters)
|
|
||||||
$1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
|
|
||||||
end
|
|
||||||
|
|
||||||
def clean_up_ajax_request_body!(body)
|
|
||||||
body.chop! if body[-1] == 0
|
|
||||||
body.gsub!(/&_=$/, '')
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
private
|
|
||||||
def get_typed_value(value)
|
|
||||||
case value
|
case value
|
||||||
when String
|
when Hash
|
||||||
value
|
if value.has_key?(:tempfile)
|
||||||
when NilClass
|
upload = value[:tempfile]
|
||||||
''
|
upload.extend(UploadedFile)
|
||||||
|
upload.original_path = value[:filename]
|
||||||
|
upload.content_type = value[:type]
|
||||||
|
upload
|
||||||
|
else
|
||||||
|
h = {}
|
||||||
|
value.each { |k, v| h[k] = normalize_parameters(v) }
|
||||||
|
h.with_indifferent_access
|
||||||
|
end
|
||||||
when Array
|
when Array
|
||||||
value.map { |v| get_typed_value(v) }
|
value.map { |e| normalize_parameters(e) }
|
||||||
else
|
else
|
||||||
if value.respond_to? :original_filename
|
|
||||||
# Uploaded file
|
|
||||||
if value.original_filename
|
|
||||||
value
|
value
|
||||||
# Multipart param
|
|
||||||
else
|
|
||||||
result = value.read
|
|
||||||
value.rewind
|
|
||||||
result
|
|
||||||
end
|
|
||||||
# Unknown value, neither string nor multipart.
|
|
||||||
else
|
|
||||||
raise "Unknown form value: #{value.inspect}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
|
|
||||||
|
|
||||||
EOL = "\015\012"
|
|
||||||
|
|
||||||
def read_multipart(body, boundary, body_size, env)
|
|
||||||
params = Hash.new([])
|
|
||||||
boundary = "--" + boundary
|
|
||||||
quoted_boundary = Regexp.quote(boundary)
|
|
||||||
buf = ""
|
|
||||||
bufsize = 10 * 1024
|
|
||||||
boundary_end=""
|
|
||||||
|
|
||||||
# start multipart/form-data
|
|
||||||
body.binmode if defined? body.binmode
|
|
||||||
case body
|
|
||||||
when File
|
|
||||||
body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding)
|
|
||||||
when StringIO
|
|
||||||
body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding)
|
|
||||||
end
|
|
||||||
boundary_size = boundary.size + EOL.size
|
|
||||||
body_size -= boundary_size
|
|
||||||
status = body.read(boundary_size)
|
|
||||||
if nil == status
|
|
||||||
raise EOFError, "no content body"
|
|
||||||
elsif boundary + EOL != status
|
|
||||||
raise EOFError, "bad content body"
|
|
||||||
end
|
|
||||||
|
|
||||||
loop do
|
|
||||||
head = nil
|
|
||||||
content =
|
|
||||||
if 10240 < body_size
|
|
||||||
UploadedTempfile.new("CGI")
|
|
||||||
else
|
|
||||||
UploadedStringIO.new
|
|
||||||
end
|
|
||||||
content.binmode if defined? content.binmode
|
|
||||||
|
|
||||||
until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
|
|
||||||
|
|
||||||
if (not head) and /#{EOL}#{EOL}/n.match(buf)
|
|
||||||
buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
|
|
||||||
head = $1.dup
|
|
||||||
""
|
|
||||||
end
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
if head and ( (EOL + boundary + EOL).size < buf.size )
|
|
||||||
content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
|
|
||||||
buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
c = if bufsize < body_size
|
|
||||||
body.read(bufsize)
|
|
||||||
else
|
|
||||||
body.read(body_size)
|
|
||||||
end
|
|
||||||
if c.nil? || c.empty?
|
|
||||||
raise EOFError, "bad content body"
|
|
||||||
end
|
|
||||||
buf.concat(c)
|
|
||||||
body_size -= c.size
|
|
||||||
end
|
|
||||||
|
|
||||||
buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
|
|
||||||
content.print $1
|
|
||||||
if "--" == $2
|
|
||||||
body_size = -1
|
|
||||||
end
|
|
||||||
boundary_end = $2.dup
|
|
||||||
""
|
|
||||||
end
|
|
||||||
|
|
||||||
content.rewind
|
|
||||||
|
|
||||||
head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
|
|
||||||
if filename = $1 || $2
|
|
||||||
if /Mac/ni.match(env['HTTP_USER_AGENT']) and
|
|
||||||
/Mozilla/ni.match(env['HTTP_USER_AGENT']) and
|
|
||||||
(not /MSIE/ni.match(env['HTTP_USER_AGENT']))
|
|
||||||
filename = CGI.unescape(filename)
|
|
||||||
end
|
|
||||||
content.original_path = filename.dup
|
|
||||||
end
|
|
||||||
|
|
||||||
head =~ /Content-Type: ([^\r]*)/ni
|
|
||||||
content.content_type = $1.dup if $1
|
|
||||||
|
|
||||||
head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
|
|
||||||
name = $1.dup if $1
|
|
||||||
|
|
||||||
if params.has_key?(name)
|
|
||||||
params[name].push(content)
|
|
||||||
else
|
|
||||||
params[name] = [content]
|
|
||||||
end
|
|
||||||
break if body_size == -1
|
|
||||||
end
|
|
||||||
raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
|
|
||||||
|
|
||||||
begin
|
|
||||||
body.rewind if body.respond_to?(:rewind)
|
|
||||||
rescue Errno::ESPIPE
|
|
||||||
# Handles exceptions raised by input streams that cannot be rewound
|
|
||||||
# such as when using plain CGI under Apache
|
|
||||||
end
|
|
||||||
|
|
||||||
params
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UrlEncodedPairParser < StringScanner #:nodoc:
|
|
||||||
attr_reader :top, :parent, :result
|
|
||||||
|
|
||||||
def initialize(pairs = [])
|
|
||||||
super('')
|
|
||||||
@result = {}
|
|
||||||
pairs.each { |key, value| parse(key, value) }
|
|
||||||
end
|
|
||||||
|
|
||||||
KEY_REGEXP = %r{([^\[\]=&]+)}
|
|
||||||
BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
|
|
||||||
|
|
||||||
# Parse the query string
|
|
||||||
def parse(key, value)
|
|
||||||
self.string = key
|
|
||||||
@top, @parent = result, nil
|
|
||||||
|
|
||||||
# First scan the bare key
|
|
||||||
key = scan(KEY_REGEXP) or return
|
|
||||||
key = post_key_check(key)
|
|
||||||
|
|
||||||
# Then scan as many nestings as present
|
|
||||||
until eos?
|
|
||||||
r = scan(BRACKETED_KEY_REGEXP) or return
|
|
||||||
key = self[1]
|
|
||||||
key = post_key_check(key)
|
|
||||||
end
|
|
||||||
|
|
||||||
bind(key, value)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
# After we see a key, we must look ahead to determine our next action. Cases:
|
|
||||||
#
|
|
||||||
# [] follows the key. Then the value must be an array.
|
|
||||||
# = follows the key. (A value comes next)
|
|
||||||
# & or the end of string follows the key. Then the key is a flag.
|
|
||||||
# otherwise, a hash follows the key.
|
|
||||||
def post_key_check(key)
|
|
||||||
if scan(/\[\]/) # a[b][] indicates that b is an array
|
|
||||||
container(key, Array)
|
|
||||||
nil
|
|
||||||
elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
|
|
||||||
container(key, Hash)
|
|
||||||
nil
|
|
||||||
else # End of key? We do nothing.
|
|
||||||
key
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add a container to the stack.
|
|
||||||
def container(key, klass)
|
|
||||||
type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
|
|
||||||
value = bind(key, klass.new)
|
|
||||||
type_conflict! klass, value unless value.is_a?(klass)
|
|
||||||
push(value)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Push a value onto the 'stack', which is actually only the top 2 items.
|
|
||||||
def push(value)
|
|
||||||
@parent, @top = @top, value
|
|
||||||
end
|
|
||||||
|
|
||||||
# Bind a key (which may be nil for items in an array) to the provided value.
|
|
||||||
def bind(key, value)
|
|
||||||
if top.is_a? Array
|
|
||||||
if key
|
|
||||||
if top[-1].is_a?(Hash) && ! top[-1].key?(key)
|
|
||||||
top[-1][key] = value
|
|
||||||
else
|
|
||||||
top << {key => value}.with_indifferent_access
|
|
||||||
push top.last
|
|
||||||
value = top[key]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
top << value
|
|
||||||
end
|
|
||||||
elsif top.is_a? Hash
|
|
||||||
key = CGI.unescape(key)
|
|
||||||
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
|
|
||||||
top[key] ||= value
|
|
||||||
return top[key]
|
|
||||||
else
|
|
||||||
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
|
|
||||||
end
|
|
||||||
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
|
|
||||||
def type_conflict!(klass, value)
|
|
||||||
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module UploadedFile
|
|
||||||
def self.included(base)
|
|
||||||
base.class_eval do
|
|
||||||
attr_accessor :original_path, :content_type
|
|
||||||
alias_method :local_path, :path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Take the basename of the upload's original filename.
|
|
||||||
# This handles the full Windows paths given by Internet Explorer
|
|
||||||
# (and perhaps other broken user agents) without affecting
|
|
||||||
# those which give the lone filename.
|
|
||||||
# The Windows regexp is adapted from Perl's File::Basename.
|
|
||||||
def original_filename
|
|
||||||
unless defined? @original_filename
|
|
||||||
@original_filename =
|
|
||||||
unless original_path.blank?
|
|
||||||
if original_path =~ /^(?:.*[:\\\/])?(.*)/m
|
|
||||||
$1
|
|
||||||
else
|
|
||||||
File.basename original_path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@original_filename
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UploadedStringIO < StringIO
|
|
||||||
include UploadedFile
|
|
||||||
end
|
|
||||||
|
|
||||||
class UploadedTempfile < Tempfile
|
|
||||||
include UploadedFile
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ module ActionController #:nodoc:
|
||||||
module RequestForgeryProtection
|
module RequestForgeryProtection
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
class_inheritable_accessor :request_forgery_protection_options
|
|
||||||
self.request_forgery_protection_options = {}
|
|
||||||
helper_method :form_authenticity_token
|
helper_method :form_authenticity_token
|
||||||
helper_method :protect_against_forgery?
|
helper_method :protect_against_forgery?
|
||||||
end
|
end
|
||||||
|
|
@ -14,7 +12,7 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
# Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a
|
# Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a
|
||||||
# forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all
|
# forged link from another site, is done by embedding a token based on a random string stored in the session (which an attacker wouldn't know) in all
|
||||||
# forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
|
# forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only
|
||||||
# HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
|
# HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication
|
||||||
# scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway.
|
# scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway.
|
||||||
|
|
@ -57,12 +55,8 @@ module ActionController #:nodoc:
|
||||||
# Example:
|
# Example:
|
||||||
#
|
#
|
||||||
# class FooController < ApplicationController
|
# class FooController < ApplicationController
|
||||||
# # uses the cookie session store (then you don't need a separate :secret)
|
|
||||||
# protect_from_forgery :except => :index
|
# protect_from_forgery :except => :index
|
||||||
#
|
#
|
||||||
# # uses one of the other session stores that uses a session_id value.
|
|
||||||
# protect_from_forgery :secret => 'my-little-pony', :except => :index
|
|
||||||
#
|
|
||||||
# # you can disable csrf protection on controller-by-controller basis:
|
# # you can disable csrf protection on controller-by-controller basis:
|
||||||
# skip_before_filter :verify_authenticity_token
|
# skip_before_filter :verify_authenticity_token
|
||||||
# end
|
# end
|
||||||
|
|
@ -70,13 +64,12 @@ module ActionController #:nodoc:
|
||||||
# Valid Options:
|
# Valid Options:
|
||||||
#
|
#
|
||||||
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
|
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
|
||||||
# * <tt>:secret</tt> - Custom salt used to generate the <tt>form_authenticity_token</tt>.
|
|
||||||
# Leave this off if you are using the cookie session store.
|
|
||||||
# * <tt>:digest</tt> - Message digest used for hashing. Defaults to 'SHA1'.
|
|
||||||
def protect_from_forgery(options = {})
|
def protect_from_forgery(options = {})
|
||||||
self.request_forgery_protection_token ||= :authenticity_token
|
self.request_forgery_protection_token ||= :authenticity_token
|
||||||
before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
|
before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
|
||||||
request_forgery_protection_options.update(options)
|
if options[:secret] || options[:digest]
|
||||||
|
ActiveSupport::Deprecation.warn("protect_from_forgery only takes :only and :except options now. :digest and :secret have no effect", caller)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -88,14 +81,19 @@ module ActionController #:nodoc:
|
||||||
|
|
||||||
# Returns true or false if a request is verified. Checks:
|
# Returns true or false if a request is verified. Checks:
|
||||||
#
|
#
|
||||||
# * is the format restricted? By default, only HTML and AJAX requests are checked.
|
# * is the format restricted? By default, only HTML requests are checked.
|
||||||
# * is it a GET request? Gets should be safe and idempotent
|
# * is it a GET request? Gets should be safe and idempotent
|
||||||
# * Does the form_authenticity_token match the given _token value from the params?
|
# * Does the form_authenticity_token match the given token value from the params?
|
||||||
def verified_request?
|
def verified_request?
|
||||||
!protect_against_forgery? ||
|
!protect_against_forgery? ||
|
||||||
request.method == :get ||
|
request.method == :get ||
|
||||||
|
request.xhr? ||
|
||||||
!verifiable_request_format? ||
|
!verifiable_request_format? ||
|
||||||
form_authenticity_token == params[request_forgery_protection_token]
|
form_authenticity_token == form_authenticity_param
|
||||||
|
end
|
||||||
|
|
||||||
|
def form_authenticity_param
|
||||||
|
params[request_forgery_protection_token]
|
||||||
end
|
end
|
||||||
|
|
||||||
def verifiable_request_format?
|
def verifiable_request_format?
|
||||||
|
|
@ -105,32 +103,7 @@ module ActionController #:nodoc:
|
||||||
# Sets the token value for the current session. Pass a <tt>:secret</tt> option
|
# Sets the token value for the current session. Pass a <tt>:secret</tt> option
|
||||||
# in +protect_from_forgery+ to add a custom salt to the hash.
|
# in +protect_from_forgery+ to add a custom salt to the hash.
|
||||||
def form_authenticity_token
|
def form_authenticity_token
|
||||||
@form_authenticity_token ||= if !session.respond_to?(:session_id)
|
session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
|
||||||
raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session."
|
|
||||||
elsif request_forgery_protection_options[:secret]
|
|
||||||
authenticity_token_from_session_id
|
|
||||||
elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest)
|
|
||||||
authenticity_token_from_cookie_session
|
|
||||||
else
|
|
||||||
raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Generates a unique digest using the session_id and the CSRF secret.
|
|
||||||
def authenticity_token_from_session_id
|
|
||||||
key = if request_forgery_protection_options[:secret].respond_to?(:call)
|
|
||||||
request_forgery_protection_options[:secret].call(@session)
|
|
||||||
else
|
|
||||||
request_forgery_protection_options[:secret]
|
|
||||||
end
|
|
||||||
digest = request_forgery_protection_options[:digest] ||= 'SHA1'
|
|
||||||
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(digest), key.to_s, session.session_id.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
# No secret was given, so assume this is a cookie session store.
|
|
||||||
def authenticity_token_from_cookie_session
|
|
||||||
session[:csrf_id] ||= CGI::Session.generate_unique_id
|
|
||||||
session.dbman.generate_digest(session[:csrf_id])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def protect_against_forgery?
|
def protect_against_forgery?
|
||||||
|
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
require 'optparse'
|
|
||||||
require 'action_controller/integration'
|
|
||||||
|
|
||||||
module ActionController
|
|
||||||
class RequestProfiler
|
|
||||||
# Wrap up the integration session runner.
|
|
||||||
class Sandbox
|
|
||||||
include Integration::Runner
|
|
||||||
|
|
||||||
def self.benchmark(n, script)
|
|
||||||
new(script).benchmark(n)
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(script_path)
|
|
||||||
@quiet = false
|
|
||||||
define_run_method(script_path)
|
|
||||||
reset!
|
|
||||||
end
|
|
||||||
|
|
||||||
def benchmark(n, profiling = false)
|
|
||||||
@quiet = true
|
|
||||||
print ' '
|
|
||||||
|
|
||||||
result = Benchmark.realtime do
|
|
||||||
n.times do |i|
|
|
||||||
run(profiling)
|
|
||||||
print_progress(i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
puts
|
|
||||||
result
|
|
||||||
ensure
|
|
||||||
@quiet = false
|
|
||||||
end
|
|
||||||
|
|
||||||
def say(message)
|
|
||||||
puts " #{message}" unless @quiet
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def define_run_method(script_path)
|
|
||||||
script = File.read(script_path)
|
|
||||||
|
|
||||||
source = <<-end_source
|
|
||||||
def run(profiling = false)
|
|
||||||
if profiling
|
|
||||||
RubyProf.resume do
|
|
||||||
#{script}
|
|
||||||
end
|
|
||||||
else
|
|
||||||
#{script}
|
|
||||||
end
|
|
||||||
|
|
||||||
old_request_count = request_count
|
|
||||||
reset!
|
|
||||||
self.request_count = old_request_count
|
|
||||||
end
|
|
||||||
end_source
|
|
||||||
|
|
||||||
instance_eval source, script_path, 1
|
|
||||||
end
|
|
||||||
|
|
||||||
def print_progress(i)
|
|
||||||
print "\n " if i % 60 == 0
|
|
||||||
print ' ' if i % 10 == 0
|
|
||||||
print '.'
|
|
||||||
$stdout.flush
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
attr_reader :options
|
|
||||||
|
|
||||||
def initialize(options = {})
|
|
||||||
@options = default_options.merge(options)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def self.run(args = nil, options = {})
|
|
||||||
profiler = new(options)
|
|
||||||
profiler.parse_options(args) if args
|
|
||||||
profiler.run
|
|
||||||
end
|
|
||||||
|
|
||||||
def run
|
|
||||||
sandbox = Sandbox.new(options[:script])
|
|
||||||
|
|
||||||
puts 'Warming up once'
|
|
||||||
|
|
||||||
elapsed = warmup(sandbox)
|
|
||||||
puts '%.2f sec, %d requests, %d req/sec' % [elapsed, sandbox.request_count, sandbox.request_count / elapsed]
|
|
||||||
puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x"
|
|
||||||
|
|
||||||
options[:benchmark] ? benchmark(sandbox) : profile(sandbox)
|
|
||||||
end
|
|
||||||
|
|
||||||
def profile(sandbox)
|
|
||||||
load_ruby_prof
|
|
||||||
|
|
||||||
benchmark(sandbox, true)
|
|
||||||
results = RubyProf.stop
|
|
||||||
|
|
||||||
show_profile_results results
|
|
||||||
results
|
|
||||||
end
|
|
||||||
|
|
||||||
def benchmark(sandbox, profiling = false)
|
|
||||||
sandbox.request_count = 0
|
|
||||||
elapsed = sandbox.benchmark(options[:n], profiling).to_f
|
|
||||||
count = sandbox.request_count.to_i
|
|
||||||
puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed]
|
|
||||||
end
|
|
||||||
|
|
||||||
def warmup(sandbox)
|
|
||||||
Benchmark.realtime { sandbox.run(false) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_options
|
|
||||||
{ :n => 100, :open => 'open %s &' }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parse command-line options
|
|
||||||
def parse_options(args)
|
|
||||||
OptionParser.new do |opt|
|
|
||||||
opt.banner = "USAGE: #{$0} [options] [session script path]"
|
|
||||||
|
|
||||||
opt.on('-n', '--times [100]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i if v }
|
|
||||||
opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
|
|
||||||
opt.on('-m', '--measure [mode]', 'Which ruby-prof measure mode to use: process_time, wall_time, cpu_time, allocations, or memory. Defaults to process_time.') { |v| options[:measure] = v }
|
|
||||||
opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
|
|
||||||
opt.on('-h', '--help', 'Show this help') { puts opt; exit }
|
|
||||||
|
|
||||||
opt.parse args
|
|
||||||
|
|
||||||
if args.empty?
|
|
||||||
puts opt
|
|
||||||
exit
|
|
||||||
end
|
|
||||||
options[:script] = args.pop
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
def load_ruby_prof
|
|
||||||
begin
|
|
||||||
gem 'ruby-prof', '>= 0.6.1'
|
|
||||||
require 'ruby-prof'
|
|
||||||
if mode = options[:measure]
|
|
||||||
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
|
|
||||||
end
|
|
||||||
rescue LoadError
|
|
||||||
abort '`gem install ruby-prof` to use the profiler'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_profile_results(results)
|
|
||||||
File.open "#{RAILS_ROOT}/tmp/profile-graph.html", 'w' do |file|
|
|
||||||
RubyProf::GraphHtmlPrinter.new(results).print(file)
|
|
||||||
`#{options[:open] % file.path}` if options[:open]
|
|
||||||
end
|
|
||||||
|
|
||||||
File.open "#{RAILS_ROOT}/tmp/profile-flat.txt", 'w' do |file|
|
|
||||||
RubyProf::FlatPrinter.new(results).print(file)
|
|
||||||
`#{options[:open] % file.path}` if options[:open]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
module ActionController #:nodoc:
|
module ActionController #:nodoc:
|
||||||
# Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
|
# Actions that fail to perform as expected throw exceptions. These
|
||||||
# (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
|
# exceptions can either be rescued for the public view (with a nice
|
||||||
# is already implemented by the Action Controller, but the public view should be tailored to your specific application.
|
# user-friendly explanation) or for the developers view (with tons of
|
||||||
|
# debugging information). The developers view is already implemented by
|
||||||
|
# the Action Controller, but the public view should be tailored to your
|
||||||
|
# specific application.
|
||||||
#
|
#
|
||||||
# The default behavior for public exceptions is to render a static html file with the name of the error code thrown. If no such
|
# The default behavior for public exceptions is to render a static html
|
||||||
# file exists, an empty response is sent with the correct status code.
|
# file with the name of the error code thrown. If no such file exists, an
|
||||||
|
# empty response is sent with the correct status code.
|
||||||
#
|
#
|
||||||
# You can override what constitutes a local request by overriding the <tt>local_request?</tt> method in your own controller.
|
# You can override what constitutes a local request by overriding the
|
||||||
# Custom rescue behavior is achieved by overriding the <tt>rescue_action_in_public</tt> and <tt>rescue_action_locally</tt> methods.
|
# <tt>local_request?</tt> method in your own controller. Custom rescue
|
||||||
|
# behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
|
||||||
|
# and <tt>rescue_action_locally</tt> methods.
|
||||||
module Rescue
|
module Rescue
|
||||||
LOCALHOST = '127.0.0.1'.freeze
|
LOCALHOST = '127.0.0.1'.freeze
|
||||||
|
|
||||||
|
|
@ -32,6 +38,9 @@ module ActionController #:nodoc:
|
||||||
'ActionView::TemplateError' => 'template_error'
|
'ActionView::TemplateError' => 'template_error'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RESCUES_TEMPLATE_PATH = ActionView::Template::EagerPath.new_and_loaded(
|
||||||
|
File.join(File.dirname(__FILE__), "templates"))
|
||||||
|
|
||||||
def self.included(base) #:nodoc:
|
def self.included(base) #:nodoc:
|
||||||
base.cattr_accessor :rescue_responses
|
base.cattr_accessor :rescue_responses
|
||||||
base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
|
base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
|
||||||
|
|
@ -50,47 +59,60 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
def process_with_exception(request, response, exception) #:nodoc:
|
def call_with_exception(env, exception) #:nodoc:
|
||||||
|
request = env["action_controller.rescue.request"] ||= Request.new(env)
|
||||||
|
response = env["action_controller.rescue.response"] ||= Response.new
|
||||||
new.process(request, response, :rescue_action, exception)
|
new.process(request, response, :rescue_action, exception)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
# Exception handler called when the performance of an action raises an exception.
|
# Exception handler called when the performance of an action raises
|
||||||
|
# an exception.
|
||||||
def rescue_action(exception)
|
def rescue_action(exception)
|
||||||
rescue_with_handler(exception) || rescue_action_without_handler(exception)
|
rescue_with_handler(exception) ||
|
||||||
|
rescue_action_without_handler(exception)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Overwrite to implement custom logging of errors. By default logs as fatal.
|
# Overwrite to implement custom logging of errors. By default
|
||||||
|
# logs as fatal.
|
||||||
def log_error(exception) #:doc:
|
def log_error(exception) #:doc:
|
||||||
ActiveSupport::Deprecation.silence do
|
ActiveSupport::Deprecation.silence do
|
||||||
if ActionView::TemplateError === exception
|
if ActionView::TemplateError === exception
|
||||||
logger.fatal(exception.to_s)
|
logger.fatal(exception.to_s)
|
||||||
else
|
else
|
||||||
logger.fatal(
|
logger.fatal(
|
||||||
"\n\n#{exception.class} (#{exception.message}):\n " +
|
"\n#{exception.class} (#{exception.message}):\n " +
|
||||||
clean_backtrace(exception).join("\n ") +
|
clean_backtrace(exception).join("\n ") + "\n\n"
|
||||||
"\n\n"
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
|
# Overwrite to implement public exception handling (for requests
|
||||||
# default will call render_optional_error_file. Override this method to provide more user friendly error messages.
|
# answering false to <tt>local_request?</tt>). By default will call
|
||||||
|
# render_optional_error_file. Override this method to provide more
|
||||||
|
# user friendly error messages.
|
||||||
def rescue_action_in_public(exception) #:doc:
|
def rescue_action_in_public(exception) #:doc:
|
||||||
render_optional_error_file response_code_for_rescue(exception)
|
render_optional_error_file response_code_for_rescue(exception)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Attempts to render a static error page based on the <tt>status_code</tt> thrown,
|
# Attempts to render a static error page based on the
|
||||||
# or just return headers if no such file exists. For example, if a 500 error is
|
# <tt>status_code</tt> thrown, or just return headers if no such file
|
||||||
# being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.
|
# exists. At first, it will try to render a localized static page.
|
||||||
# If the file doesn't exist, the body of the response will be left empty.
|
# For example, if a 500 error is being handled Rails and locale is :da,
|
||||||
|
# it will first attempt to render the file at <tt>public/500.da.html</tt>
|
||||||
|
# then attempt to render <tt>public/500.html</tt>. If none of them exist,
|
||||||
|
# the body of the response will be left empty.
|
||||||
def render_optional_error_file(status_code)
|
def render_optional_error_file(status_code)
|
||||||
status = interpret_status(status_code)
|
status = interpret_status(status_code)
|
||||||
|
locale_path = "#{Rails.public_path}/#{status[0,3]}.#{I18n.locale}.html" if I18n.locale
|
||||||
path = "#{Rails.public_path}/#{status[0,3]}.html"
|
path = "#{Rails.public_path}/#{status[0,3]}.html"
|
||||||
if File.exist?(path)
|
|
||||||
render :file => path, :status => status
|
if locale_path && File.exist?(locale_path)
|
||||||
|
render :file => locale_path, :status => status, :content_type => Mime::HTML
|
||||||
|
elsif File.exist?(path)
|
||||||
|
render :file => path, :status => status, :content_type => Mime::HTML
|
||||||
else
|
else
|
||||||
head status
|
head status
|
||||||
end
|
end
|
||||||
|
|
@ -107,11 +129,13 @@ module ActionController #:nodoc:
|
||||||
# a controller action.
|
# a controller action.
|
||||||
def rescue_action_locally(exception)
|
def rescue_action_locally(exception)
|
||||||
@template.instance_variable_set("@exception", exception)
|
@template.instance_variable_set("@exception", exception)
|
||||||
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
|
@template.instance_variable_set("@rescues_path", RESCUES_TEMPLATE_PATH)
|
||||||
@template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
|
@template.instance_variable_set("@contents",
|
||||||
|
@template.render(:file => template_path_for_local_rescue(exception)))
|
||||||
|
|
||||||
response.content_type = Mime::HTML
|
response.content_type = Mime::HTML
|
||||||
render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
|
render_for_file(rescues_path("layout"),
|
||||||
|
response_code_for_rescue(exception))
|
||||||
end
|
end
|
||||||
|
|
||||||
def rescue_action_without_handler(exception)
|
def rescue_action_without_handler(exception)
|
||||||
|
|
@ -139,7 +163,7 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def rescues_path(template_name)
|
def rescues_path(template_name)
|
||||||
"#{File.dirname(__FILE__)}/templates/rescues/#{template_name}.erb"
|
RESCUES_TEMPLATE_PATH["rescues/#{template_name}.erb"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def template_path_for_local_rescue(exception)
|
def template_path_for_local_rescue(exception)
|
||||||
|
|
@ -151,13 +175,9 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def clean_backtrace(exception)
|
def clean_backtrace(exception)
|
||||||
if backtrace = exception.backtrace
|
defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
|
||||||
if defined?(RAILS_ROOT)
|
Rails.backtrace_cleaner.clean(exception.backtrace) :
|
||||||
backtrace.map { |line| line.sub RAILS_ROOT, '' }
|
exception.backtrace
|
||||||
else
|
|
||||||
backtrace
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ module ActionController
|
||||||
#
|
#
|
||||||
# Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
|
# Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
|
||||||
module Resources
|
module Resources
|
||||||
INHERITABLE_OPTIONS = :namespace, :shallow, :actions
|
INHERITABLE_OPTIONS = :namespace, :shallow
|
||||||
|
|
||||||
class Resource #:nodoc:
|
class Resource #:nodoc:
|
||||||
DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
|
DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
|
||||||
|
|
@ -91,7 +91,7 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def shallow_path_prefix
|
def shallow_path_prefix
|
||||||
@shallow_path_prefix ||= "#{path_prefix unless @options[:shallow]}"
|
@shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
|
||||||
end
|
end
|
||||||
|
|
||||||
def member_path
|
def member_path
|
||||||
|
|
@ -103,7 +103,7 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def shallow_name_prefix
|
def shallow_name_prefix
|
||||||
@shallow_name_prefix ||= "#{name_prefix unless @options[:shallow]}"
|
@shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
|
||||||
end
|
end
|
||||||
|
|
||||||
def nesting_name_prefix
|
def nesting_name_prefix
|
||||||
|
|
@ -119,7 +119,7 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_action?(action)
|
def has_action?(action)
|
||||||
!DEFAULT_ACTIONS.include?(action) || @options[:actions].nil? || @options[:actions].include?(action)
|
!DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
@ -135,22 +135,27 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_allowed_actions
|
def set_allowed_actions
|
||||||
only = @options.delete(:only)
|
only, except = @options.values_at(:only, :except)
|
||||||
except = @options.delete(:except)
|
@allowed_actions ||= {}
|
||||||
|
|
||||||
if only && except
|
if only == :all || except == :none
|
||||||
raise ArgumentError, 'Please supply either :only or :except, not both.'
|
only = nil
|
||||||
elsif only == :all || except == :none
|
except = []
|
||||||
options[:actions] = DEFAULT_ACTIONS
|
|
||||||
elsif only == :none || except == :all
|
elsif only == :none || except == :all
|
||||||
options[:actions] = []
|
only = []
|
||||||
elsif only
|
except = nil
|
||||||
options[:actions] = DEFAULT_ACTIONS & Array(only).map(&:to_sym)
|
|
||||||
elsif except
|
|
||||||
options[:actions] = DEFAULT_ACTIONS - Array(except).map(&:to_sym)
|
|
||||||
else
|
|
||||||
# leave options[:actions] alone
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if only
|
||||||
|
@allowed_actions[:only] = Array(only).map(&:to_sym)
|
||||||
|
elsif except
|
||||||
|
@allowed_actions[:except] = Array(except).map(&:to_sym)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def action_allowed?(action)
|
||||||
|
only, except = @allowed_actions.values_at(:only, :except)
|
||||||
|
(!only || only.include?(action)) && (!except || !except.include?(action))
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_prefixes
|
def set_prefixes
|
||||||
|
|
@ -283,7 +288,12 @@ module ActionController
|
||||||
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
|
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
|
||||||
# * <tt>:controller</tt> - Specify the controller name for the routes.
|
# * <tt>:controller</tt> - Specify the controller name for the routes.
|
||||||
# * <tt>:singular</tt> - Specify the singular name used in the member routes.
|
# * <tt>:singular</tt> - Specify the singular name used in the member routes.
|
||||||
# * <tt>:requirements</tt> - Set custom routing parameter requirements.
|
# * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either
|
||||||
|
# regular expressions (which must match for the route to match) or extra parameters. For example:
|
||||||
|
#
|
||||||
|
# map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
|
||||||
|
#
|
||||||
|
# will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
|
||||||
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
|
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
|
||||||
# * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
|
# * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
|
||||||
# # products_path == '/productos'
|
# # products_path == '/productos'
|
||||||
|
|
@ -307,9 +317,10 @@ module ActionController
|
||||||
# notes.resources :attachments
|
# notes.resources :attachments
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# * <tt>:path_names</tt> - Specify different names for the 'new' and 'edit' actions. For example:
|
# * <tt>:path_names</tt> - Specify different path names for the actions. For example:
|
||||||
# # new_products_path == '/productos/nuevo'
|
# # new_products_path == '/productos/nuevo'
|
||||||
# map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' }
|
# # bids_product_path(1) == '/productos/1/licitacoes'
|
||||||
|
# map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
|
||||||
#
|
#
|
||||||
# You can also set default action names from an environment, like this:
|
# You can also set default action names from an environment, like this:
|
||||||
# config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
|
# config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
|
||||||
|
|
@ -398,8 +409,6 @@ module ActionController
|
||||||
# # --> POST /posts/1/comments (maps to the CommentsController#create action)
|
# # --> POST /posts/1/comments (maps to the CommentsController#create action)
|
||||||
# # --> PUT /posts/1/comments/1 (fails)
|
# # --> PUT /posts/1/comments/1 (fails)
|
||||||
#
|
#
|
||||||
# The <tt>:only</tt> and <tt>:except</tt> options are inherited by any nested resource(s).
|
|
||||||
#
|
|
||||||
# If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
|
# If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
|
|
@ -517,16 +526,16 @@ module ActionController
|
||||||
resource = Resource.new(entities, options)
|
resource = Resource.new(entities, options)
|
||||||
|
|
||||||
with_options :controller => resource.controller do |map|
|
with_options :controller => resource.controller do |map|
|
||||||
map_collection_actions(map, resource)
|
|
||||||
map_default_collection_actions(map, resource)
|
|
||||||
map_new_actions(map, resource)
|
|
||||||
map_member_actions(map, resource)
|
|
||||||
|
|
||||||
map_associations(resource, options)
|
map_associations(resource, options)
|
||||||
|
|
||||||
if block_given?
|
if block_given?
|
||||||
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
|
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
map_collection_actions(map, resource)
|
||||||
|
map_default_collection_actions(map, resource)
|
||||||
|
map_new_actions(map, resource)
|
||||||
|
map_member_actions(map, resource)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -534,16 +543,16 @@ module ActionController
|
||||||
resource = SingletonResource.new(entities, options)
|
resource = SingletonResource.new(entities, options)
|
||||||
|
|
||||||
with_options :controller => resource.controller do |map|
|
with_options :controller => resource.controller do |map|
|
||||||
map_collection_actions(map, resource)
|
|
||||||
map_default_singleton_actions(map, resource)
|
|
||||||
map_new_actions(map, resource)
|
|
||||||
map_member_actions(map, resource)
|
|
||||||
|
|
||||||
map_associations(resource, options)
|
map_associations(resource, options)
|
||||||
|
|
||||||
if block_given?
|
if block_given?
|
||||||
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
|
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
map_collection_actions(map, resource)
|
||||||
|
map_new_actions(map, resource)
|
||||||
|
map_member_actions(map, resource)
|
||||||
|
map_default_singleton_actions(map, resource)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -578,7 +587,10 @@ module ActionController
|
||||||
resource.collection_methods.each do |method, actions|
|
resource.collection_methods.each do |method, actions|
|
||||||
actions.each do |action|
|
actions.each do |action|
|
||||||
[method].flatten.each do |m|
|
[method].flatten.each do |m|
|
||||||
map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
|
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
|
||||||
|
action_path ||= action
|
||||||
|
|
||||||
|
map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -622,7 +634,7 @@ module ActionController
|
||||||
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
|
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
|
||||||
action_path ||= Base.resources_path_names[action] || action
|
action_path ||= Base.resources_path_names[action] || action
|
||||||
|
|
||||||
map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m)
|
map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -633,16 +645,14 @@ module ActionController
|
||||||
map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
|
map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil)
|
def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
|
||||||
if resource.has_action?(action)
|
if resource.has_action?(action)
|
||||||
action_options = action_options_for(action, resource, method)
|
action_options = action_options_for(action, resource, method, resource_options)
|
||||||
formatted_route_path = "#{route_path}.:format"
|
formatted_route_path = "#{route_path}.:format"
|
||||||
|
|
||||||
if route_name && @set.named_routes[route_name.to_sym].nil?
|
if route_name && @set.named_routes[route_name.to_sym].nil?
|
||||||
map.named_route(route_name, route_path, action_options)
|
map.named_route(route_name, formatted_route_path, action_options)
|
||||||
map.named_route("formatted_#{route_name}", formatted_route_path, action_options)
|
|
||||||
else
|
else
|
||||||
map.connect(route_path, action_options)
|
|
||||||
map.connect(formatted_route_path, action_options)
|
map.connect(formatted_route_path, action_options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -654,9 +664,10 @@ module ActionController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def action_options_for(action, resource, method = nil)
|
def action_options_for(action, resource, method = nil, resource_options = {})
|
||||||
default_options = { :action => action.to_s }
|
default_options = { :action => action.to_s }
|
||||||
require_id = !resource.kind_of?(SingletonResource)
|
require_id = !resource.kind_of?(SingletonResource)
|
||||||
|
force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
|
||||||
|
|
||||||
case default_options[:action]
|
case default_options[:action]
|
||||||
when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
|
when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
|
||||||
|
|
@ -664,12 +675,8 @@ module ActionController
|
||||||
when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
|
when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
|
||||||
when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
|
when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
|
||||||
when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
|
when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
|
||||||
else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements)
|
else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ActionController::Routing::RouteSet::Mapper
|
|
||||||
include ActionController::Resources
|
|
||||||
end
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,25 @@
|
||||||
require 'digest/md5'
|
require 'digest/md5'
|
||||||
|
|
||||||
module ActionController # :nodoc:
|
module ActionController # :nodoc:
|
||||||
# Represents an HTTP response generated by a controller action. One can use an
|
# Represents an HTTP response generated by a controller action. One can use
|
||||||
# ActionController::AbstractResponse object to retrieve the current state of the
|
# an ActionController::Response object to retrieve the current state
|
||||||
# response, or customize the response. An AbstractResponse object can either
|
# of the response, or customize the response. An Response object can
|
||||||
# represent a "real" HTTP response (i.e. one that is meant to be sent back to the
|
# either represent a "real" HTTP response (i.e. one that is meant to be sent
|
||||||
# web browser) or a test response (i.e. one that is generated from integration
|
# back to the web browser) or a test response (i.e. one that is generated
|
||||||
# tests). See CgiResponse and TestResponse, respectively.
|
# from integration tests). See CgiResponse and TestResponse, respectively.
|
||||||
#
|
#
|
||||||
# AbstractResponse is mostly a Ruby on Rails framework implement detail, and should
|
# Response is mostly a Ruby on Rails framework implement detail, and
|
||||||
# never be used directly in controllers. Controllers should use the methods defined
|
# should never be used directly in controllers. Controllers should use the
|
||||||
# in ActionController::Base instead. For example, if you want to set the HTTP
|
# methods defined in ActionController::Base instead. For example, if you want
|
||||||
# response's content MIME type, then use ActionControllerBase#headers instead of
|
# to set the HTTP response's content MIME type, then use
|
||||||
# AbstractResponse#headers.
|
# ActionControllerBase#headers instead of Response#headers.
|
||||||
#
|
#
|
||||||
# Nevertheless, integration tests may want to inspect controller responses in more
|
# Nevertheless, integration tests may want to inspect controller responses in
|
||||||
# detail, and that's when AbstractResponse can be useful for application developers.
|
# more detail, and that's when Response can be useful for application
|
||||||
# Integration test methods such as ActionController::Integration::Session#get and
|
# developers. Integration test methods such as
|
||||||
# ActionController::Integration::Session#post return objects of type TestResponse
|
# ActionController::Integration::Session#get and
|
||||||
# (which are of course also of type AbstractResponse).
|
# ActionController::Integration::Session#post return objects of type
|
||||||
|
# TestResponse (which are of course also of type Response).
|
||||||
#
|
#
|
||||||
# For example, the following demo integration "test" prints the body of the
|
# For example, the following demo integration "test" prints the body of the
|
||||||
# controller response to the console:
|
# controller response to the console:
|
||||||
|
|
@ -29,25 +30,26 @@ module ActionController # :nodoc:
|
||||||
# puts @response.body
|
# puts @response.body
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
class AbstractResponse
|
class Response < Rack::Response
|
||||||
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
|
DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
|
||||||
attr_accessor :request
|
attr_accessor :request
|
||||||
|
|
||||||
# The body content (e.g. HTML) of the response, as a String.
|
attr_accessor :session, :assigns, :template, :layout
|
||||||
attr_accessor :body
|
|
||||||
# The headers of the response, as a Hash. It maps header names to header values.
|
|
||||||
attr_accessor :headers
|
|
||||||
attr_accessor :session, :cookies, :assigns, :template, :layout
|
|
||||||
attr_accessor :redirected_to, :redirected_to_method_params
|
attr_accessor :redirected_to, :redirected_to_method_params
|
||||||
|
|
||||||
delegate :default_charset, :to => 'ActionController::Base'
|
delegate :default_charset, :to => 'ActionController::Base'
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
|
@status = 200
|
||||||
end
|
@header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS)
|
||||||
|
|
||||||
def status; headers['Status'] end
|
@writer = lambda { |x| @body << x }
|
||||||
def status=(status) headers['Status'] = status end
|
@block = nil
|
||||||
|
|
||||||
|
@body = "",
|
||||||
|
@session = []
|
||||||
|
@assigns = []
|
||||||
|
end
|
||||||
|
|
||||||
def location; headers['Location'] end
|
def location; headers['Location'] end
|
||||||
def location=(url) headers['Location'] = url end
|
def location=(url) headers['Location'] = url end
|
||||||
|
|
@ -115,8 +117,12 @@ module ActionController # :nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def etag=(etag)
|
def etag=(etag)
|
||||||
|
if etag.blank?
|
||||||
|
headers.delete('ETag')
|
||||||
|
else
|
||||||
headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
|
headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def redirect(url, status)
|
def redirect(url, status)
|
||||||
self.status = status
|
self.status = status
|
||||||
|
|
@ -138,6 +144,44 @@ module ActionController # :nodoc:
|
||||||
handle_conditional_get!
|
handle_conditional_get!
|
||||||
set_content_length!
|
set_content_length!
|
||||||
convert_content_type!
|
convert_content_type!
|
||||||
|
convert_language!
|
||||||
|
convert_cookies!
|
||||||
|
end
|
||||||
|
|
||||||
|
def each(&callback)
|
||||||
|
if @body.respond_to?(:call)
|
||||||
|
@writer = lambda { |x| callback.call(x) }
|
||||||
|
@body.call(self, self)
|
||||||
|
elsif @body.respond_to?(:to_str)
|
||||||
|
yield @body
|
||||||
|
else
|
||||||
|
@body.each(&callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
@writer = callback
|
||||||
|
@block.call(self) if @block
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(str)
|
||||||
|
@writer.call str.to_s
|
||||||
|
str
|
||||||
|
end
|
||||||
|
|
||||||
|
def flush #:nodoc:
|
||||||
|
ActiveSupport::Deprecation.warn(
|
||||||
|
'Calling output.flush is no longer needed for streaming output ' +
|
||||||
|
'because ActionController::Response automatically handles it', caller)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_cookie(key, value)
|
||||||
|
if value.has_key?(:http_only)
|
||||||
|
ActiveSupport::Deprecation.warn(
|
||||||
|
"The :http_only option in ActionController::Response#set_cookie " +
|
||||||
|
"has been renamed. Please use :httponly instead.", caller)
|
||||||
|
value[:httponly] ||= value.delete(:http_only)
|
||||||
|
end
|
||||||
|
|
||||||
|
super(key, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
@ -157,7 +201,7 @@ module ActionController # :nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def nonempty_ok_response?
|
def nonempty_ok_response?
|
||||||
ok = !status || status[0..2] == '200'
|
ok = !status || status.to_s[0..2] == '200'
|
||||||
ok && body.is_a?(String) && !body.empty?
|
ok && body.is_a?(String) && !body.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -168,23 +212,28 @@ module ActionController # :nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def convert_content_type!
|
def convert_content_type!
|
||||||
if content_type = headers.delete("Content-Type")
|
headers['Content-Type'] ||= "text/html"
|
||||||
self.headers["type"] = content_type
|
headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset']
|
||||||
end
|
end
|
||||||
if content_type = headers.delete("Content-type")
|
|
||||||
self.headers["type"] = content_type
|
# Don't set the Content-Length for block-based bodies as that would mean
|
||||||
end
|
# reading it all into memory. Not nice for, say, a 2GB streaming file.
|
||||||
if content_type = headers.delete("content-type")
|
def set_content_length!
|
||||||
self.headers["type"] = content_type
|
if status && status.to_s[0..2] == '204'
|
||||||
|
headers.delete('Content-Length')
|
||||||
|
elsif length = headers['Content-Length']
|
||||||
|
headers['Content-Length'] = length.to_s
|
||||||
|
elsif !body.respond_to?(:call) && (!status || status.to_s[0..2] != '304')
|
||||||
|
headers["Content-Length"] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice
|
def convert_language!
|
||||||
# for, say, a 2GB streaming file.
|
headers["Content-Language"] = headers.delete("language") if headers["language"]
|
||||||
def set_content_length!
|
|
||||||
unless body.respond_to?(:call) || (status && status[0..2] == '304')
|
|
||||||
self.headers["Content-Length"] ||= body.size
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def convert_cookies!
|
||||||
|
headers['Set-Cookie'] = Array(headers['Set-Cookie']).compact
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
require 'cgi'
|
require 'cgi'
|
||||||
require 'uri'
|
require 'uri'
|
||||||
require 'action_controller/polymorphic_routes'
|
|
||||||
require 'action_controller/routing/optimisations'
|
require 'action_controller/routing/optimisations'
|
||||||
require 'action_controller/routing/routing_ext'
|
require 'action_controller/routing/routing_ext'
|
||||||
require 'action_controller/routing/route'
|
require 'action_controller/routing/route'
|
||||||
|
|
@ -84,9 +83,11 @@ module ActionController
|
||||||
# This sets up +blog+ as the default controller if no other is specified.
|
# This sets up +blog+ as the default controller if no other is specified.
|
||||||
# This means visiting '/' would invoke the blog controller.
|
# This means visiting '/' would invoke the blog controller.
|
||||||
#
|
#
|
||||||
# More formally, you can define defaults in a route with the <tt>:defaults</tt> key.
|
# More formally, you can include arbitrary parameters in the route, thus:
|
||||||
#
|
#
|
||||||
# map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
|
# map.connect ':controller/:action/:id', :action => 'show', :page => 'Dashboard'
|
||||||
|
#
|
||||||
|
# This will pass the :page parameter to all incoming requests that match this route.
|
||||||
#
|
#
|
||||||
# Note: The default routes, as provided by the Rails generator, make all actions in every
|
# Note: The default routes, as provided by the Rails generator, make all actions in every
|
||||||
# controller accessible via GET requests. You should consider removing them or commenting
|
# controller accessible via GET requests. You should consider removing them or commenting
|
||||||
|
|
@ -192,9 +193,8 @@ module ActionController
|
||||||
#
|
#
|
||||||
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
|
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
|
||||||
#
|
#
|
||||||
# will glob all remaining parts of the route that were not recognized earlier. This idiom
|
# will glob all remaining parts of the route that were not recognized earlier.
|
||||||
# must appear at the end of the path. The globbed values are in <tt>params[:path]</tt> in
|
# The globbed values are in <tt>params[:path]</tt> as an array of path segments.
|
||||||
# this case.
|
|
||||||
#
|
#
|
||||||
# == Route conditions
|
# == Route conditions
|
||||||
#
|
#
|
||||||
|
|
@ -267,10 +267,13 @@ module ActionController
|
||||||
module Routing
|
module Routing
|
||||||
SEPARATORS = %w( / . ? )
|
SEPARATORS = %w( / . ? )
|
||||||
|
|
||||||
HTTP_METHODS = [:get, :head, :post, :put, :delete]
|
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options]
|
||||||
|
|
||||||
ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
|
ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
|
||||||
|
|
||||||
|
mattr_accessor :generate_best_match
|
||||||
|
self.generate_best_match = true
|
||||||
|
|
||||||
# The root paths which may contain controller files
|
# The root paths which may contain controller files
|
||||||
mattr_accessor :controller_paths
|
mattr_accessor :controller_paths
|
||||||
self.controller_paths = []
|
self.controller_paths = []
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ module ActionController
|
||||||
def segment_for(string)
|
def segment_for(string)
|
||||||
segment =
|
segment =
|
||||||
case string
|
case string
|
||||||
|
when /\A\.(:format)?\//
|
||||||
|
OptionalFormatSegment.new
|
||||||
when /\A:(\w+)/
|
when /\A:(\w+)/
|
||||||
key = $1.to_sym
|
key = $1.to_sym
|
||||||
key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key)
|
key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key)
|
||||||
|
|
@ -157,7 +159,8 @@ module ActionController
|
||||||
path = "/#{path}" unless path[0] == ?/
|
path = "/#{path}" unless path[0] == ?/
|
||||||
path = "#{path}/" unless path[-1] == ?/
|
path = "#{path}/" unless path[-1] == ?/
|
||||||
|
|
||||||
path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix]
|
prefix = options[:path_prefix].to_s.gsub(/^\//,'')
|
||||||
|
path = "/#{prefix}#{path}" unless prefix.blank?
|
||||||
|
|
||||||
segments = segments_for_route_path(path)
|
segments = segments_for_route_path(path)
|
||||||
defaults, requirements, conditions = divide_route_options(segments, options)
|
defaults, requirements, conditions = divide_route_options(segments, options)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ module ActionController
|
||||||
# rather than triggering the expensive logic in +url_for+.
|
# rather than triggering the expensive logic in +url_for+.
|
||||||
class PositionalArguments < Optimiser
|
class PositionalArguments < Optimiser
|
||||||
def guard_conditions
|
def guard_conditions
|
||||||
number_of_arguments = route.segment_keys.size
|
number_of_arguments = route.required_segment_keys.size
|
||||||
# if they're using foo_url(:id=>2) it's one
|
# if they're using foo_url(:id=>2) it's one
|
||||||
# argument, but we don't want to generate /foos/id2
|
# argument, but we don't want to generate /foos/id2
|
||||||
if number_of_arguments == 1
|
if number_of_arguments == 1
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ module ActionController
|
||||||
result = recognize_optimized(path, environment) and return result
|
result = recognize_optimized(path, environment) and return result
|
||||||
|
|
||||||
# Route was not recognized. Try to find out why (maybe wrong verb).
|
# Route was not recognized. Try to find out why (maybe wrong verb).
|
||||||
allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }
|
allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, environment.merge(:method => verb)) } }
|
||||||
|
|
||||||
if environment[:method] && !HTTP_METHODS.include?(environment[:method])
|
if environment[:method] && !HTTP_METHODS.include?(environment[:method])
|
||||||
raise NotImplemented.new(*allows)
|
raise NotImplemented.new(*allows)
|
||||||
|
|
@ -98,7 +98,6 @@ module ActionController
|
||||||
if Array === item
|
if Array === item
|
||||||
i += 1
|
i += 1
|
||||||
start = (i == 1)
|
start = (i == 1)
|
||||||
final = (i == list.size)
|
|
||||||
tag, sub = item
|
tag, sub = item
|
||||||
if tag == :dynamic
|
if tag == :dynamic
|
||||||
body += padding + "#{start ? 'if' : 'elsif'} true\n"
|
body += padding + "#{start ? 'if' : 'elsif'} true\n"
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,11 @@ module ActionController
|
||||||
end.compact
|
end.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def required_segment_keys
|
||||||
|
required_segments = segments.select {|seg| (!seg.optional? && !seg.is_a?(DividerSegment)) || seg.is_a?(PathSegment) }
|
||||||
|
required_segments.collect { |seg| seg.key if seg.respond_to?(:key)}.compact
|
||||||
|
end
|
||||||
|
|
||||||
# Build a query string from the keys of the given hash. If +only_keys+
|
# Build a query string from the keys of the given hash. If +only_keys+
|
||||||
# is given (as an array), only the keys indicated will be used to build
|
# is given (as an array), only the keys indicated will be used to build
|
||||||
# the query string. The query string will correctly build array parameter
|
# the query string. The query string will correctly build array parameter
|
||||||
|
|
@ -122,6 +127,16 @@ module ActionController
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def generate(options, hash, expire_on = {})
|
||||||
|
path, hash = generate_raw(options, hash, expire_on)
|
||||||
|
append_query_string(path, hash, extra_keys(options))
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_extras(options, hash, expire_on = {})
|
||||||
|
path, hash = generate_raw(options, hash, expire_on)
|
||||||
|
[path, extra_keys(options)]
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def requirement_for(key)
|
def requirement_for(key)
|
||||||
return requirements[key] if requirements.key? key
|
return requirements[key] if requirements.key? key
|
||||||
|
|
@ -150,11 +165,6 @@ module ActionController
|
||||||
# the query string. (Never use keys from the recalled request when building the
|
# the query string. (Never use keys from the recalled request when building the
|
||||||
# query string.)
|
# query string.)
|
||||||
|
|
||||||
method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
|
|
||||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
|
||||||
|
|
||||||
method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
|
|
||||||
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
|
|
||||||
raw_method
|
raw_method
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ module ActionController
|
||||||
# Mapper instances have relatively few instance methods, in order to avoid
|
# Mapper instances have relatively few instance methods, in order to avoid
|
||||||
# clashes with named routes.
|
# clashes with named routes.
|
||||||
class Mapper #:doc:
|
class Mapper #:doc:
|
||||||
|
include ActionController::Resources
|
||||||
|
|
||||||
def initialize(set) #:nodoc:
|
def initialize(set) #:nodoc:
|
||||||
@set = set
|
@set = set
|
||||||
end
|
end
|
||||||
|
|
@ -136,13 +138,17 @@ module ActionController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def named_helper_module_eval(code, *args)
|
||||||
|
@module.module_eval(code, *args)
|
||||||
|
end
|
||||||
|
|
||||||
def define_hash_access(route, name, kind, options)
|
def define_hash_access(route, name, kind, options)
|
||||||
selector = hash_access_name(name, kind)
|
selector = hash_access_name(name, kind)
|
||||||
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
|
||||||
def #{selector}(options = nil)
|
def #{selector}(options = nil) # def hash_for_users_url(options = nil)
|
||||||
options ? #{options.inspect}.merge(options) : #{options.inspect}
|
options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false}
|
||||||
end
|
end # end
|
||||||
protected :#{selector}
|
protected :#{selector} # protected :hash_for_users_url
|
||||||
end_eval
|
end_eval
|
||||||
helpers << selector
|
helpers << selector
|
||||||
end
|
end
|
||||||
|
|
@ -166,33 +172,44 @@ module ActionController
|
||||||
#
|
#
|
||||||
# foo_url(bar, baz, bang, :sort_by => 'baz')
|
# foo_url(bar, baz, bang, :sort_by => 'baz')
|
||||||
#
|
#
|
||||||
@module.module_eval <<-end_eval # We use module_eval to avoid leaks
|
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
|
||||||
def #{selector}(*args)
|
def #{selector}(*args) # def users_url(*args)
|
||||||
|
#
|
||||||
#{generate_optimisation_block(route, kind)}
|
#{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)}
|
||||||
|
#
|
||||||
opts = if args.empty? || Hash === args.first
|
opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
|
||||||
args.first || {}
|
args.first || {} # args.first || {}
|
||||||
else
|
else # else
|
||||||
options = args.extract_options!
|
options = args.extract_options! # options = args.extract_options!
|
||||||
args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
|
args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
|
||||||
h[k] = v
|
h[k] = v # h[k] = v
|
||||||
h
|
h # h
|
||||||
end
|
end # end
|
||||||
options.merge(args)
|
options.merge(args) # options.merge(args)
|
||||||
end
|
end # end
|
||||||
|
#
|
||||||
url_for(#{hash_access_method}(opts))
|
url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
|
||||||
end
|
#
|
||||||
protected :#{selector}
|
end # end
|
||||||
|
#Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL.
|
||||||
|
def formatted_#{selector}(*args) # def formatted_users_url(*args)
|
||||||
|
ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn(
|
||||||
|
"formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " +
|
||||||
|
"Please pass format to the standard " + # "Please pass format to the standard " +
|
||||||
|
"#{selector} method instead.", caller) # "users_url method instead.", caller)
|
||||||
|
#{selector}(*args) # users_url(*args)
|
||||||
|
end # end
|
||||||
|
protected :#{selector} # protected :users_url
|
||||||
end_eval
|
end_eval
|
||||||
helpers << selector
|
helpers << selector
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :routes, :named_routes, :configuration_file
|
attr_accessor :routes, :named_routes, :configuration_files
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
|
self.configuration_files = []
|
||||||
|
|
||||||
self.routes = []
|
self.routes = []
|
||||||
self.named_routes = NamedRouteCollection.new
|
self.named_routes = NamedRouteCollection.new
|
||||||
|
|
||||||
|
|
@ -206,7 +223,6 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def draw
|
def draw
|
||||||
clear!
|
|
||||||
yield Mapper.new(self)
|
yield Mapper.new(self)
|
||||||
install_helpers
|
install_helpers
|
||||||
end
|
end
|
||||||
|
|
@ -230,8 +246,22 @@ module ActionController
|
||||||
routes.empty?
|
routes.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_configuration_file(path)
|
||||||
|
self.configuration_files << path
|
||||||
|
end
|
||||||
|
|
||||||
|
# Deprecated accessor
|
||||||
|
def configuration_file=(path)
|
||||||
|
add_configuration_file(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Deprecated accessor
|
||||||
|
def configuration_file
|
||||||
|
configuration_files
|
||||||
|
end
|
||||||
|
|
||||||
def load!
|
def load!
|
||||||
Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
|
Routing.use_controllers!(nil) # Clear the controller cache so we may discover new ones
|
||||||
clear!
|
clear!
|
||||||
load_routes!
|
load_routes!
|
||||||
end
|
end
|
||||||
|
|
@ -240,26 +270,42 @@ module ActionController
|
||||||
alias reload! load!
|
alias reload! load!
|
||||||
|
|
||||||
def reload
|
def reload
|
||||||
if @routes_last_modified && configuration_file
|
if configuration_files.any? && @routes_last_modified
|
||||||
mtime = File.stat(configuration_file).mtime
|
if routes_changed_at == @routes_last_modified
|
||||||
# if it hasn't been changed, then just return
|
return # routes didn't change, don't reload
|
||||||
return if mtime == @routes_last_modified
|
else
|
||||||
# if it has changed then record the new time and fall to the load! below
|
@routes_last_modified = routes_changed_at
|
||||||
@routes_last_modified = mtime
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
load!
|
load!
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_routes!
|
def load_routes!
|
||||||
if configuration_file
|
if configuration_files.any?
|
||||||
load configuration_file
|
configuration_files.each { |config| load(config) }
|
||||||
@routes_last_modified = File.stat(configuration_file).mtime
|
@routes_last_modified = routes_changed_at
|
||||||
else
|
else
|
||||||
add_route ":controller/:action/:id"
|
add_route ":controller/:action/:id"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def routes_changed_at
|
||||||
|
routes_changed_at = nil
|
||||||
|
|
||||||
|
configuration_files.each do |config|
|
||||||
|
config_changed_at = File.stat(config).mtime
|
||||||
|
|
||||||
|
if routes_changed_at.nil? || config_changed_at > routes_changed_at
|
||||||
|
routes_changed_at = config_changed_at
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
routes_changed_at
|
||||||
|
end
|
||||||
|
|
||||||
def add_route(path, options = {})
|
def add_route(path, options = {})
|
||||||
|
options.each { |k, v| options[k] = v.to_s if [:controller, :action].include?(k) && v.is_a?(Symbol) }
|
||||||
route = builder.build(path, options)
|
route = builder.build(path, options)
|
||||||
routes << route
|
routes << route
|
||||||
route
|
route
|
||||||
|
|
@ -359,11 +405,14 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
# don't use the recalled keys when determining which routes to check
|
# don't use the recalled keys when determining which routes to check
|
||||||
routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
|
future_routes, deprecated_routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }]
|
||||||
|
routes = Routing.generate_best_match ? deprecated_routes : future_routes
|
||||||
|
|
||||||
routes.each do |route|
|
routes.each_with_index do |route, index|
|
||||||
results = route.__send__(method, options, merged, expire_on)
|
results = route.__send__(method, options, merged, expire_on)
|
||||||
return results if results && (!results.is_a?(Array) || results.first)
|
if results && (!results.is_a?(Array) || results.first)
|
||||||
|
return results
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -382,10 +431,16 @@ module ActionController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
request = Request.new(env)
|
||||||
|
app = Routing::Routes.recognize(request)
|
||||||
|
app.call(env).to_a
|
||||||
|
end
|
||||||
|
|
||||||
def recognize(request)
|
def recognize(request)
|
||||||
params = recognize_path(request.path, extract_request_environment(request))
|
params = recognize_path(request.path, extract_request_environment(request))
|
||||||
request.path_parameters = params.with_indifferent_access
|
request.path_parameters = params.with_indifferent_access
|
||||||
"#{params[:controller].camelize}Controller".constantize
|
"#{params[:controller].to_s.camelize}Controller".constantize
|
||||||
end
|
end
|
||||||
|
|
||||||
def recognize_path(path, environment={})
|
def recognize_path(path, environment={})
|
||||||
|
|
@ -396,7 +451,10 @@ module ActionController
|
||||||
@routes_by_controller ||= Hash.new do |controller_hash, controller|
|
@routes_by_controller ||= Hash.new do |controller_hash, controller|
|
||||||
controller_hash[controller] = Hash.new do |action_hash, action|
|
controller_hash[controller] = Hash.new do |action_hash, action|
|
||||||
action_hash[action] = Hash.new do |key_hash, keys|
|
action_hash[action] = Hash.new do |key_hash, keys|
|
||||||
key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
|
key_hash[keys] = [
|
||||||
|
routes_for_controller_and_action_and_keys(controller, action, keys),
|
||||||
|
deprecated_routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||||
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -408,10 +466,11 @@ module ActionController
|
||||||
merged = options if expire_on[:controller]
|
merged = options if expire_on[:controller]
|
||||||
action = merged[:action] || 'index'
|
action = merged[:action] || 'index'
|
||||||
|
|
||||||
routes_by_controller[controller][action][merged.keys]
|
routes_by_controller[controller][action][merged.keys][1]
|
||||||
end
|
end
|
||||||
|
|
||||||
def routes_for_controller_and_action(controller, action)
|
def routes_for_controller_and_action(controller, action)
|
||||||
|
ActiveSupport::Deprecation.warn "routes_for_controller_and_action() has been deprecated. Please use routes_for()"
|
||||||
selected = routes.select do |route|
|
selected = routes.select do |route|
|
||||||
route.matches_controller_and_action? controller, action
|
route.matches_controller_and_action? controller, action
|
||||||
end
|
end
|
||||||
|
|
@ -419,6 +478,12 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def routes_for_controller_and_action_and_keys(controller, action, keys)
|
def routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||||
|
routes.select do |route|
|
||||||
|
route.matches_controller_and_action? controller, action
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def deprecated_routes_for_controller_and_action_and_keys(controller, action, keys)
|
||||||
selected = routes.select do |route|
|
selected = routes.select do |route|
|
||||||
route.matches_controller_and_action? controller, action
|
route.matches_controller_and_action? controller, action
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ module ActionController
|
||||||
class Segment #:nodoc:
|
class Segment #:nodoc:
|
||||||
RESERVED_PCHAR = ':@&=+$,;'
|
RESERVED_PCHAR = ':@&=+$,;'
|
||||||
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
|
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
|
||||||
|
if RUBY_VERSION >= '1.9'
|
||||||
|
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
|
||||||
|
else
|
||||||
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
|
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
|
||||||
|
end
|
||||||
|
|
||||||
# TODO: Convert :is_optional accessor to read only
|
# TODO: Convert :is_optional accessor to read only
|
||||||
attr_accessor :is_optional
|
attr_accessor :is_optional
|
||||||
|
|
@ -191,23 +195,19 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def regexp_chunk
|
def regexp_chunk
|
||||||
if regexp
|
regexp ? regexp_string : default_regexp_chunk
|
||||||
if regexp_has_modifiers?
|
|
||||||
"(#{regexp.to_s})"
|
|
||||||
else
|
|
||||||
"(#{regexp.source})"
|
|
||||||
end
|
end
|
||||||
else
|
|
||||||
|
def regexp_string
|
||||||
|
regexp_has_modifiers? ? "(#{regexp.to_s})" : "(#{regexp.source})"
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_regexp_chunk
|
||||||
"([^#{Routing::SEPARATORS.join}]+)"
|
"([^#{Routing::SEPARATORS.join}]+)"
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def number_of_captures
|
def number_of_captures
|
||||||
if regexp
|
regexp ? regexp.number_of_captures + 1 : 1
|
||||||
regexp.number_of_captures + 1
|
|
||||||
else
|
|
||||||
1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_pattern(pattern)
|
def build_pattern(pattern)
|
||||||
|
|
@ -244,10 +244,6 @@ module ActionController
|
||||||
"(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
|
"(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
|
||||||
end
|
end
|
||||||
|
|
||||||
def number_of_captures
|
|
||||||
1
|
|
||||||
end
|
|
||||||
|
|
||||||
# Don't URI.escape the controller name since it may contain slashes.
|
# Don't URI.escape the controller name since it may contain slashes.
|
||||||
def interpolation_chunk(value_code = local_name)
|
def interpolation_chunk(value_code = local_name)
|
||||||
"\#{#{value_code}.to_s}"
|
"\#{#{value_code}.to_s}"
|
||||||
|
|
@ -289,8 +285,8 @@ module ActionController
|
||||||
"params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
|
"params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def regexp_chunk
|
def default_regexp_chunk
|
||||||
regexp || "(.*)"
|
"(.*)"
|
||||||
end
|
end
|
||||||
|
|
||||||
def number_of_captures
|
def number_of_captures
|
||||||
|
|
@ -308,5 +304,40 @@ module ActionController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The OptionalFormatSegment allows for any resource route to have an optional
|
||||||
|
# :format, which decreases the amount of routes created by 50%.
|
||||||
|
class OptionalFormatSegment < DynamicSegment
|
||||||
|
|
||||||
|
def initialize(key = nil, options = {})
|
||||||
|
super(:format, {:optional => true}.merge(options))
|
||||||
|
end
|
||||||
|
|
||||||
|
def interpolation_chunk
|
||||||
|
"." + super
|
||||||
|
end
|
||||||
|
|
||||||
|
def regexp_chunk
|
||||||
|
'/|(\.[^/?\.]+)?'
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
'(.:format)?'
|
||||||
|
end
|
||||||
|
|
||||||
|
def extract_value
|
||||||
|
"#{local_name} = options[:#{key}] && options[:#{key}].to_s.downcase"
|
||||||
|
end
|
||||||
|
|
||||||
|
#the value should not include the period (.)
|
||||||
|
def match_extraction(next_capture)
|
||||||
|
%[
|
||||||
|
if (m = match[#{next_capture}])
|
||||||
|
params[:#{key}] = URI.unescape(m.from(1))
|
||||||
|
end
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
181
vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb
vendored
Normal file
181
vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb
vendored
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
require 'rack/utils'
|
||||||
|
|
||||||
|
module ActionController
|
||||||
|
module Session
|
||||||
|
class AbstractStore
|
||||||
|
ENV_SESSION_KEY = 'rack.session'.freeze
|
||||||
|
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
|
||||||
|
|
||||||
|
HTTP_COOKIE = 'HTTP_COOKIE'.freeze
|
||||||
|
SET_COOKIE = 'Set-Cookie'.freeze
|
||||||
|
|
||||||
|
class SessionHash < Hash
|
||||||
|
def initialize(by, env)
|
||||||
|
super()
|
||||||
|
@by = by
|
||||||
|
@env = env
|
||||||
|
@loaded = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def session_id
|
||||||
|
ActiveSupport::Deprecation.warn(
|
||||||
|
"ActionController::Session::AbstractStore::SessionHash#session_id " +
|
||||||
|
"has been deprecated. Please use request.session_options[:id] instead.", caller)
|
||||||
|
@env[ENV_SESSION_OPTIONS_KEY][:id]
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](key)
|
||||||
|
load! unless @loaded
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def []=(key, value)
|
||||||
|
load! unless @loaded
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_hash
|
||||||
|
h = {}.replace(self)
|
||||||
|
h.delete_if { |k,v| v.nil? }
|
||||||
|
h
|
||||||
|
end
|
||||||
|
|
||||||
|
def data
|
||||||
|
ActiveSupport::Deprecation.warn(
|
||||||
|
"ActionController::Session::AbstractStore::SessionHash#data " +
|
||||||
|
"has been deprecated. Please use #to_hash instead.", caller)
|
||||||
|
to_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
load! unless @loaded
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def loaded?
|
||||||
|
@loaded
|
||||||
|
end
|
||||||
|
|
||||||
|
def load!
|
||||||
|
stale_session_check! do
|
||||||
|
id, session = @by.send(:load_session, @env)
|
||||||
|
(@env[ENV_SESSION_OPTIONS_KEY] ||= {})[:id] = id
|
||||||
|
replace(session)
|
||||||
|
@loaded = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def stale_session_check!
|
||||||
|
yield
|
||||||
|
rescue ArgumentError => argument_error
|
||||||
|
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
||||||
|
begin
|
||||||
|
# Note that the regexp does not allow $1 to end with a ':'
|
||||||
|
$1.constantize
|
||||||
|
rescue LoadError, NameError => const_error
|
||||||
|
raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: \#{const_error.message} [\#{const_error.class}])\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
retry
|
||||||
|
else
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
DEFAULT_OPTIONS = {
|
||||||
|
:key => '_session_id',
|
||||||
|
:path => '/',
|
||||||
|
:domain => nil,
|
||||||
|
:expire_after => nil,
|
||||||
|
:secure => false,
|
||||||
|
:httponly => true,
|
||||||
|
:cookie_only => true
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize(app, options = {})
|
||||||
|
# Process legacy CGI options
|
||||||
|
options = options.symbolize_keys
|
||||||
|
if options.has_key?(:session_path)
|
||||||
|
options[:path] = options.delete(:session_path)
|
||||||
|
end
|
||||||
|
if options.has_key?(:session_key)
|
||||||
|
options[:key] = options.delete(:session_key)
|
||||||
|
end
|
||||||
|
if options.has_key?(:session_http_only)
|
||||||
|
options[:httponly] = options.delete(:session_http_only)
|
||||||
|
end
|
||||||
|
|
||||||
|
@app = app
|
||||||
|
@default_options = DEFAULT_OPTIONS.merge(options)
|
||||||
|
@key = @default_options[:key]
|
||||||
|
@cookie_only = @default_options[:cookie_only]
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
session = SessionHash.new(self, env)
|
||||||
|
|
||||||
|
env[ENV_SESSION_KEY] = session
|
||||||
|
env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
|
||||||
|
|
||||||
|
response = @app.call(env)
|
||||||
|
|
||||||
|
session_data = env[ENV_SESSION_KEY]
|
||||||
|
options = env[ENV_SESSION_OPTIONS_KEY]
|
||||||
|
|
||||||
|
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
|
||||||
|
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
|
||||||
|
|
||||||
|
sid = options[:id] || generate_sid
|
||||||
|
|
||||||
|
unless set_session(env, sid, session_data.to_hash)
|
||||||
|
return response
|
||||||
|
end
|
||||||
|
|
||||||
|
cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid)
|
||||||
|
cookie << "; domain=#{options[:domain]}" if options[:domain]
|
||||||
|
cookie << "; path=#{options[:path]}" if options[:path]
|
||||||
|
if options[:expire_after]
|
||||||
|
expiry = Time.now + options[:expire_after]
|
||||||
|
cookie << "; expires=#{expiry.httpdate}"
|
||||||
|
end
|
||||||
|
cookie << "; Secure" if options[:secure]
|
||||||
|
cookie << "; HttpOnly" if options[:httponly]
|
||||||
|
|
||||||
|
headers = response[1]
|
||||||
|
unless headers[SET_COOKIE].blank?
|
||||||
|
headers[SET_COOKIE] << "\n#{cookie}"
|
||||||
|
else
|
||||||
|
headers[SET_COOKIE] = cookie
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
response
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def generate_sid
|
||||||
|
ActiveSupport::SecureRandom.hex(16)
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_session(env)
|
||||||
|
request = Rack::Request.new(env)
|
||||||
|
sid = request.cookies[@key]
|
||||||
|
unless @cookie_only
|
||||||
|
sid ||= request.params[@key]
|
||||||
|
end
|
||||||
|
sid, session = get_session(env, sid)
|
||||||
|
[sid, session]
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_session(env, sid)
|
||||||
|
raise '#get_session needs to be implemented.'
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_session(env, sid, session_data)
|
||||||
|
raise '#set_session needs to be implemented.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,340 +0,0 @@
|
||||||
require 'cgi'
|
|
||||||
require 'cgi/session'
|
|
||||||
require 'digest/md5'
|
|
||||||
|
|
||||||
class CGI
|
|
||||||
class Session
|
|
||||||
attr_reader :data
|
|
||||||
|
|
||||||
# Return this session's underlying Session instance. Useful for the DB-backed session stores.
|
|
||||||
def model
|
|
||||||
@dbman.model if @dbman
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# A session store backed by an Active Record class. A default class is
|
|
||||||
# provided, but any object duck-typing to an Active Record Session class
|
|
||||||
# with text +session_id+ and +data+ attributes is sufficient.
|
|
||||||
#
|
|
||||||
# The default assumes a +sessions+ tables with columns:
|
|
||||||
# +id+ (numeric primary key),
|
|
||||||
# +session_id+ (text, or longtext if your session data exceeds 65K), and
|
|
||||||
# +data+ (text or longtext; careful if your session data exceeds 65KB).
|
|
||||||
# The +session_id+ column should always be indexed for speedy lookups.
|
|
||||||
# Session data is marshaled to the +data+ column in Base64 format.
|
|
||||||
# If the data you write is larger than the column's size limit,
|
|
||||||
# ActionController::SessionOverflowError will be raised.
|
|
||||||
#
|
|
||||||
# You may configure the table name, primary key, and data column.
|
|
||||||
# For example, at the end of <tt>config/environment.rb</tt>:
|
|
||||||
# CGI::Session::ActiveRecordStore::Session.table_name = 'legacy_session_table'
|
|
||||||
# CGI::Session::ActiveRecordStore::Session.primary_key = 'session_id'
|
|
||||||
# CGI::Session::ActiveRecordStore::Session.data_column_name = 'legacy_session_data'
|
|
||||||
# Note that setting the primary key to the +session_id+ frees you from
|
|
||||||
# having a separate +id+ column if you don't want it. However, you must
|
|
||||||
# set <tt>session.model.id = session.session_id</tt> by hand! A before filter
|
|
||||||
# on ApplicationController is a good place.
|
|
||||||
#
|
|
||||||
# Since the default class is a simple Active Record, you get timestamps
|
|
||||||
# for free if you add +created_at+ and +updated_at+ datetime columns to
|
|
||||||
# the +sessions+ table, making periodic session expiration a snap.
|
|
||||||
#
|
|
||||||
# You may provide your own session class implementation, whether a
|
|
||||||
# feature-packed Active Record or a bare-metal high-performance SQL
|
|
||||||
# store, by setting
|
|
||||||
# CGI::Session::ActiveRecordStore.session_class = MySessionClass
|
|
||||||
# You must implement these methods:
|
|
||||||
# self.find_by_session_id(session_id)
|
|
||||||
# initialize(hash_of_session_id_and_data)
|
|
||||||
# attr_reader :session_id
|
|
||||||
# attr_accessor :data
|
|
||||||
# save
|
|
||||||
# destroy
|
|
||||||
#
|
|
||||||
# The example SqlBypass class is a generic SQL session store. You may
|
|
||||||
# use it as a basis for high-performance database-specific stores.
|
|
||||||
class ActiveRecordStore
|
|
||||||
# The default Active Record class.
|
|
||||||
class Session < ActiveRecord::Base
|
|
||||||
# Customizable data column name. Defaults to 'data'.
|
|
||||||
cattr_accessor :data_column_name
|
|
||||||
self.data_column_name = 'data'
|
|
||||||
|
|
||||||
before_save :marshal_data!
|
|
||||||
before_save :raise_on_session_data_overflow!
|
|
||||||
|
|
||||||
class << self
|
|
||||||
# Don't try to reload ARStore::Session in dev mode.
|
|
||||||
def reloadable? #:nodoc:
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def data_column_size_limit
|
|
||||||
@data_column_size_limit ||= columns_hash[@@data_column_name].limit
|
|
||||||
end
|
|
||||||
|
|
||||||
# Hook to set up sessid compatibility.
|
|
||||||
def find_by_session_id(session_id)
|
|
||||||
setup_sessid_compatibility!
|
|
||||||
find_by_session_id(session_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def marshal(data) ActiveSupport::Base64.encode64(Marshal.dump(data)) if data end
|
|
||||||
def unmarshal(data) Marshal.load(ActiveSupport::Base64.decode64(data)) if data end
|
|
||||||
|
|
||||||
def create_table!
|
|
||||||
connection.execute <<-end_sql
|
|
||||||
CREATE TABLE #{table_name} (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
#{connection.quote_column_name('session_id')} TEXT UNIQUE,
|
|
||||||
#{connection.quote_column_name(@@data_column_name)} TEXT(255)
|
|
||||||
)
|
|
||||||
end_sql
|
|
||||||
end
|
|
||||||
|
|
||||||
def drop_table!
|
|
||||||
connection.execute "DROP TABLE #{table_name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
# Compatibility with tables using sessid instead of session_id.
|
|
||||||
def setup_sessid_compatibility!
|
|
||||||
# Reset column info since it may be stale.
|
|
||||||
reset_column_information
|
|
||||||
if columns_hash['sessid']
|
|
||||||
def self.find_by_session_id(*args)
|
|
||||||
find_by_sessid(*args)
|
|
||||||
end
|
|
||||||
|
|
||||||
define_method(:session_id) { sessid }
|
|
||||||
define_method(:session_id=) { |session_id| self.sessid = session_id }
|
|
||||||
else
|
|
||||||
def self.find_by_session_id(session_id)
|
|
||||||
find :first, :conditions => ["session_id #{attribute_condition(session_id)}", session_id]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Lazy-unmarshal session state.
|
|
||||||
def data
|
|
||||||
@data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_writer :data
|
|
||||||
|
|
||||||
# Has the session been loaded yet?
|
|
||||||
def loaded?
|
|
||||||
!! @data
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def marshal_data!
|
|
||||||
return false if !loaded?
|
|
||||||
write_attribute(@@data_column_name, self.class.marshal(self.data))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Ensures that the data about to be stored in the database is not
|
|
||||||
# larger than the data storage column. Raises
|
|
||||||
# ActionController::SessionOverflowError.
|
|
||||||
def raise_on_session_data_overflow!
|
|
||||||
return false if !loaded?
|
|
||||||
limit = self.class.data_column_size_limit
|
|
||||||
if loaded? and limit and read_attribute(@@data_column_name).size > limit
|
|
||||||
raise ActionController::SessionOverflowError
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# A barebones session store which duck-types with the default session
|
|
||||||
# store but bypasses Active Record and issues SQL directly. This is
|
|
||||||
# an example session model class meant as a basis for your own classes.
|
|
||||||
#
|
|
||||||
# The database connection, table name, and session id and data columns
|
|
||||||
# are configurable class attributes. Marshaling and unmarshaling
|
|
||||||
# are implemented as class methods that you may override. By default,
|
|
||||||
# marshaling data is
|
|
||||||
#
|
|
||||||
# ActiveSupport::Base64.encode64(Marshal.dump(data))
|
|
||||||
#
|
|
||||||
# and unmarshaling data is
|
|
||||||
#
|
|
||||||
# Marshal.load(ActiveSupport::Base64.decode64(data))
|
|
||||||
#
|
|
||||||
# This marshaling behavior is intended to store the widest range of
|
|
||||||
# binary session data in a +text+ column. For higher performance,
|
|
||||||
# store in a +blob+ column instead and forgo the Base64 encoding.
|
|
||||||
class SqlBypass
|
|
||||||
# Use the ActiveRecord::Base.connection by default.
|
|
||||||
cattr_accessor :connection
|
|
||||||
|
|
||||||
# The table name defaults to 'sessions'.
|
|
||||||
cattr_accessor :table_name
|
|
||||||
@@table_name = 'sessions'
|
|
||||||
|
|
||||||
# The session id field defaults to 'session_id'.
|
|
||||||
cattr_accessor :session_id_column
|
|
||||||
@@session_id_column = 'session_id'
|
|
||||||
|
|
||||||
# The data field defaults to 'data'.
|
|
||||||
cattr_accessor :data_column
|
|
||||||
@@data_column = 'data'
|
|
||||||
|
|
||||||
class << self
|
|
||||||
|
|
||||||
def connection
|
|
||||||
@@connection ||= ActiveRecord::Base.connection
|
|
||||||
end
|
|
||||||
|
|
||||||
# Look up a session by id and unmarshal its data if found.
|
|
||||||
def find_by_session_id(session_id)
|
|
||||||
if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
|
|
||||||
new(:session_id => session_id, :marshaled_data => record['data'])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def marshal(data) ActiveSupport::Base64.encode64(Marshal.dump(data)) if data end
|
|
||||||
def unmarshal(data) Marshal.load(ActiveSupport::Base64.decode64(data)) if data end
|
|
||||||
|
|
||||||
def create_table!
|
|
||||||
@@connection.execute <<-end_sql
|
|
||||||
CREATE TABLE #{table_name} (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
#{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
|
|
||||||
#{@@connection.quote_column_name(data_column)} TEXT
|
|
||||||
)
|
|
||||||
end_sql
|
|
||||||
end
|
|
||||||
|
|
||||||
def drop_table!
|
|
||||||
@@connection.execute "DROP TABLE #{table_name}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :session_id
|
|
||||||
attr_writer :data
|
|
||||||
|
|
||||||
# Look for normal and marshaled data, self.find_by_session_id's way of
|
|
||||||
# telling us to postpone unmarshaling until the data is requested.
|
|
||||||
# We need to handle a normal data attribute in case of a new record.
|
|
||||||
def initialize(attributes)
|
|
||||||
@session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
|
|
||||||
@new_record = @marshaled_data.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
def new_record?
|
|
||||||
@new_record
|
|
||||||
end
|
|
||||||
|
|
||||||
# Lazy-unmarshal session state.
|
|
||||||
def data
|
|
||||||
unless @data
|
|
||||||
if @marshaled_data
|
|
||||||
@data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
|
|
||||||
else
|
|
||||||
@data = {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@data
|
|
||||||
end
|
|
||||||
|
|
||||||
def loaded?
|
|
||||||
!! @data
|
|
||||||
end
|
|
||||||
|
|
||||||
def save
|
|
||||||
return false if !loaded?
|
|
||||||
marshaled_data = self.class.marshal(data)
|
|
||||||
|
|
||||||
if @new_record
|
|
||||||
@new_record = false
|
|
||||||
@@connection.update <<-end_sql, 'Create session'
|
|
||||||
INSERT INTO #{@@table_name} (
|
|
||||||
#{@@connection.quote_column_name(@@session_id_column)},
|
|
||||||
#{@@connection.quote_column_name(@@data_column)} )
|
|
||||||
VALUES (
|
|
||||||
#{@@connection.quote(session_id)},
|
|
||||||
#{@@connection.quote(marshaled_data)} )
|
|
||||||
end_sql
|
|
||||||
else
|
|
||||||
@@connection.update <<-end_sql, 'Update session'
|
|
||||||
UPDATE #{@@table_name}
|
|
||||||
SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
|
|
||||||
WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
|
|
||||||
end_sql
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
unless @new_record
|
|
||||||
@@connection.delete <<-end_sql, 'Destroy session'
|
|
||||||
DELETE FROM #{@@table_name}
|
|
||||||
WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
|
|
||||||
end_sql
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# The class used for session storage. Defaults to
|
|
||||||
# CGI::Session::ActiveRecordStore::Session.
|
|
||||||
cattr_accessor :session_class
|
|
||||||
self.session_class = Session
|
|
||||||
|
|
||||||
# Find or instantiate a session given a CGI::Session.
|
|
||||||
def initialize(session, option = nil)
|
|
||||||
session_id = session.session_id
|
|
||||||
unless @session = ActiveRecord::Base.silence { @@session_class.find_by_session_id(session_id) }
|
|
||||||
unless session.new_session
|
|
||||||
raise CGI::Session::NoSession, 'uninitialized session'
|
|
||||||
end
|
|
||||||
@session = @@session_class.new(:session_id => session_id, :data => {})
|
|
||||||
# session saving can be lazy again, because of improved component implementation
|
|
||||||
# therefore next line gets commented out:
|
|
||||||
# @session.save
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Access the underlying session model.
|
|
||||||
def model
|
|
||||||
@session
|
|
||||||
end
|
|
||||||
|
|
||||||
# Restore session state. The session model handles unmarshaling.
|
|
||||||
def restore
|
|
||||||
if @session
|
|
||||||
@session.data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Save session store.
|
|
||||||
def update
|
|
||||||
if @session
|
|
||||||
ActiveRecord::Base.silence { @session.save }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Save and close the session store.
|
|
||||||
def close
|
|
||||||
if @session
|
|
||||||
update
|
|
||||||
@session = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Delete and close the session store.
|
|
||||||
def delete
|
|
||||||
if @session
|
|
||||||
ActiveRecord::Base.silence { @session.destroy }
|
|
||||||
@session = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
def logger
|
|
||||||
ActionController::Base.logger rescue nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,83 +1,171 @@
|
||||||
require 'cgi'
|
module ActionController
|
||||||
require 'cgi/session'
|
module Session
|
||||||
require 'openssl' # to generate the HMAC message digest
|
# This cookie-based session store is the Rails default. Sessions typically
|
||||||
|
# contain at most a user_id and flash message; both fit within the 4K cookie
|
||||||
# This cookie-based session store is the Rails default. Sessions typically
|
# size limit. Cookie-based sessions are dramatically faster than the
|
||||||
# contain at most a user_id and flash message; both fit within the 4K cookie
|
# alternatives.
|
||||||
# size limit. Cookie-based sessions are dramatically faster than the
|
#
|
||||||
# alternatives.
|
# If you have more than 4K of session data or don't want your data to be
|
||||||
#
|
# visible to the user, pick another session store.
|
||||||
# If you have more than 4K of session data or don't want your data to be
|
#
|
||||||
# visible to the user, pick another session store.
|
# CookieOverflow is raised if you attempt to store more than 4K of data.
|
||||||
#
|
#
|
||||||
# CookieOverflow is raised if you attempt to store more than 4K of data.
|
# A message digest is included with the cookie to ensure data integrity:
|
||||||
# TamperedWithCookie is raised if the data integrity check fails.
|
# a user cannot alter his +user_id+ without knowing the secret key
|
||||||
#
|
# included in the hash. New apps are generated with a pregenerated secret
|
||||||
# A message digest is included with the cookie to ensure data integrity:
|
# in config/environment.rb. Set your own for old apps you're upgrading.
|
||||||
# a user cannot alter his +user_id+ without knowing the secret key included in
|
#
|
||||||
# the hash. New apps are generated with a pregenerated secret in
|
# Session options:
|
||||||
# config/environment.rb. Set your own for old apps you're upgrading.
|
#
|
||||||
#
|
# * <tt>:secret</tt>: An application-wide key string or block returning a
|
||||||
# Session options:
|
# string called per generated digest. The block is called with the
|
||||||
#
|
# CGI::Session instance as an argument. It's important that the secret
|
||||||
# * <tt>:secret</tt>: An application-wide key string or block returning a string
|
# is not vulnerable to a dictionary attack. Therefore, you should choose
|
||||||
# called per generated digest. The block is called with the CGI::Session
|
# a secret consisting of random numbers and letters and more than 30
|
||||||
# instance as an argument. It's important that the secret is not vulnerable to
|
# characters. Examples:
|
||||||
# a dictionary attack. Therefore, you should choose a secret consisting of
|
#
|
||||||
# random numbers and letters and more than 30 characters. Examples:
|
# :secret => '449fe2e7daee471bffae2fd8dc02313d'
|
||||||
#
|
# :secret => Proc.new { User.current_user.secret_key }
|
||||||
# :secret => '449fe2e7daee471bffae2fd8dc02313d'
|
#
|
||||||
# :secret => Proc.new { User.current_user.secret_key }
|
# * <tt>:digest</tt>: The message digest algorithm used to verify session
|
||||||
#
|
# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
|
||||||
# * <tt>:digest</tt>: The message digest algorithm used to verify session
|
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
|
||||||
# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
|
#
|
||||||
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
|
# To generate a secret key for an existing application, run
|
||||||
#
|
# "rake secret" and set the key in config/environment.rb.
|
||||||
# To generate a secret key for an existing application, run
|
#
|
||||||
# "rake secret" and set the key in config/environment.rb.
|
# Note that changing digest or secret invalidates all existing sessions!
|
||||||
#
|
class CookieStore
|
||||||
# Note that changing digest or secret invalidates all existing sessions!
|
|
||||||
class CGI::Session::CookieStore
|
|
||||||
# Cookies can typically store 4096 bytes.
|
# Cookies can typically store 4096 bytes.
|
||||||
MAX = 4096
|
MAX = 4096
|
||||||
SECRET_MIN_LENGTH = 30 # characters
|
SECRET_MIN_LENGTH = 30 # characters
|
||||||
|
|
||||||
|
DEFAULT_OPTIONS = {
|
||||||
|
:key => '_session_id',
|
||||||
|
:domain => nil,
|
||||||
|
:path => "/",
|
||||||
|
:expire_after => nil,
|
||||||
|
:httponly => true
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
ENV_SESSION_KEY = "rack.session".freeze
|
||||||
|
ENV_SESSION_OPTIONS_KEY = "rack.session.options".freeze
|
||||||
|
HTTP_SET_COOKIE = "Set-Cookie".freeze
|
||||||
|
|
||||||
# Raised when storing more than 4K of session data.
|
# Raised when storing more than 4K of session data.
|
||||||
class CookieOverflow < StandardError; end
|
class CookieOverflow < StandardError; end
|
||||||
|
|
||||||
# Raised when the cookie fails its integrity check.
|
def initialize(app, options = {})
|
||||||
class TamperedWithCookie < StandardError; end
|
# Process legacy CGI options
|
||||||
|
options = options.symbolize_keys
|
||||||
# Called from CGI::Session only.
|
if options.has_key?(:session_path)
|
||||||
def initialize(session, options = {})
|
options[:path] = options.delete(:session_path)
|
||||||
# The session_key option is required.
|
end
|
||||||
if options['session_key'].blank?
|
if options.has_key?(:session_key)
|
||||||
raise ArgumentError, 'A session_key is required to write a cookie containing the session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase" } in config/environment.rb'
|
options[:key] = options.delete(:session_key)
|
||||||
|
end
|
||||||
|
if options.has_key?(:session_http_only)
|
||||||
|
options[:httponly] = options.delete(:session_http_only)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@app = app
|
||||||
|
|
||||||
|
# The session_key option is required.
|
||||||
|
ensure_session_key(options[:key])
|
||||||
|
@key = options.delete(:key).freeze
|
||||||
|
|
||||||
# The secret option is required.
|
# The secret option is required.
|
||||||
ensure_secret_secure(options['secret'])
|
ensure_secret_secure(options[:secret])
|
||||||
|
@secret = options.delete(:secret).freeze
|
||||||
|
|
||||||
# Keep the session and its secret on hand so we can read and write cookies.
|
@digest = options.delete(:digest) || 'SHA1'
|
||||||
@session, @secret = session, options['secret']
|
@verifier = verifier_for(@secret, @digest)
|
||||||
|
|
||||||
# Message digest defaults to SHA1.
|
@default_options = DEFAULT_OPTIONS.merge(options).freeze
|
||||||
@digest = options['digest'] || 'SHA1'
|
|
||||||
|
|
||||||
# Default cookie options derived from session settings.
|
freeze
|
||||||
@cookie_options = {
|
end
|
||||||
'name' => options['session_key'],
|
|
||||||
'path' => options['session_path'],
|
|
||||||
'domain' => options['session_domain'],
|
|
||||||
'expires' => options['session_expires'],
|
|
||||||
'secure' => options['session_secure'],
|
|
||||||
'http_only' => options['session_http_only']
|
|
||||||
}
|
|
||||||
|
|
||||||
# Set no_hidden and no_cookies since the session id is unused and we
|
def call(env)
|
||||||
# set our own data cookie.
|
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
|
||||||
options['no_hidden'] = true
|
env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
|
||||||
options['no_cookies'] = true
|
|
||||||
|
status, headers, body = @app.call(env)
|
||||||
|
|
||||||
|
session_data = env[ENV_SESSION_KEY]
|
||||||
|
options = env[ENV_SESSION_OPTIONS_KEY]
|
||||||
|
|
||||||
|
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
|
||||||
|
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
|
||||||
|
session_data = marshal(session_data.to_hash)
|
||||||
|
|
||||||
|
raise CookieOverflow if session_data.size > MAX
|
||||||
|
|
||||||
|
cookie = Hash.new
|
||||||
|
cookie[:value] = session_data
|
||||||
|
unless options[:expire_after].nil?
|
||||||
|
cookie[:expires] = Time.now + options[:expire_after]
|
||||||
|
end
|
||||||
|
|
||||||
|
cookie = build_cookie(@key, cookie.merge(options))
|
||||||
|
unless headers[HTTP_SET_COOKIE].blank?
|
||||||
|
headers[HTTP_SET_COOKIE] << "\n#{cookie}"
|
||||||
|
else
|
||||||
|
headers[HTTP_SET_COOKIE] = cookie
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
[status, headers, body]
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
# Should be in Rack::Utils soon
|
||||||
|
def build_cookie(key, value)
|
||||||
|
case value
|
||||||
|
when Hash
|
||||||
|
domain = "; domain=" + value[:domain] if value[:domain]
|
||||||
|
path = "; path=" + value[:path] if value[:path]
|
||||||
|
# According to RFC 2109, we need dashes here.
|
||||||
|
# N.B.: cgi.rb uses spaces...
|
||||||
|
expires = "; expires=" + value[:expires].clone.gmtime.
|
||||||
|
strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
|
||||||
|
secure = "; secure" if value[:secure]
|
||||||
|
httponly = "; HttpOnly" if value[:httponly]
|
||||||
|
value = value[:value]
|
||||||
|
end
|
||||||
|
value = [value] unless Array === value
|
||||||
|
cookie = Rack::Utils.escape(key) + "=" +
|
||||||
|
value.map { |v| Rack::Utils.escape(v) }.join("&") +
|
||||||
|
"#{domain}#{path}#{expires}#{secure}#{httponly}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_session(env)
|
||||||
|
request = Rack::Request.new(env)
|
||||||
|
session_data = request.cookies[@key]
|
||||||
|
data = unmarshal(session_data) || persistent_session_id!({})
|
||||||
|
[data[:session_id], data]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Marshal a session hash into safe cookie data. Include an integrity hash.
|
||||||
|
def marshal(session)
|
||||||
|
@verifier.generate(persistent_session_id!(session))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Unmarshal cookie data to a hash and verify its integrity.
|
||||||
|
def unmarshal(cookie)
|
||||||
|
persistent_session_id!(@verifier.verify(cookie)) if cookie
|
||||||
|
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_session_key(key)
|
||||||
|
if key.blank?
|
||||||
|
raise ArgumentError, 'A key is required to write a ' +
|
||||||
|
'cookie containing the session data. Use ' +
|
||||||
|
'config.action_controller.session = { :key => ' +
|
||||||
|
'"_myapp_session", :secret => "some secret phrase" } in ' +
|
||||||
|
'config/environment.rb'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# To prevent users from using something insecure like "Password" we make sure that the
|
# To prevent users from using something insecure like "Password" we make sure that the
|
||||||
|
|
@ -88,80 +176,46 @@ class CGI::Session::CookieStore
|
||||||
return true if secret.is_a?(Proc)
|
return true if secret.is_a?(Proc)
|
||||||
|
|
||||||
if secret.blank?
|
if secret.blank?
|
||||||
raise ArgumentError, %Q{A secret is required to generate an integrity hash for cookie session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase of at least #{SECRET_MIN_LENGTH} characters" } in config/environment.rb}
|
raise ArgumentError, "A secret is required to generate an " +
|
||||||
|
"integrity hash for cookie session data. Use " +
|
||||||
|
"config.action_controller.session = { :key => " +
|
||||||
|
"\"_myapp_session\", :secret => \"some secret phrase of at " +
|
||||||
|
"least #{SECRET_MIN_LENGTH} characters\" } " +
|
||||||
|
"in config/environment.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
if secret.length < SECRET_MIN_LENGTH
|
if secret.length < SECRET_MIN_LENGTH
|
||||||
raise ArgumentError, %Q{Secret should be something secure, like "#{CGI::Session.generate_unique_id}". The value you provided, "#{secret}", is shorter than the minimum length of #{SECRET_MIN_LENGTH} characters}
|
raise ArgumentError, "Secret should be something secure, " +
|
||||||
|
"like \"#{ActiveSupport::SecureRandom.hex(16)}\". The value you " +
|
||||||
|
"provided, \"#{secret}\", is shorter than the minimum length " +
|
||||||
|
"of #{SECRET_MIN_LENGTH} characters"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Restore session data from the cookie.
|
def verifier_for(secret, digest)
|
||||||
def restore
|
key = secret.respond_to?(:call) ? secret.call : secret
|
||||||
@original = read_cookie
|
ActiveSupport::MessageVerifier.new(key, digest)
|
||||||
@data = unmarshal(@original) || {}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Wait until close to write the session data cookie.
|
def generate_sid
|
||||||
def update; end
|
ActiveSupport::SecureRandom.hex(16)
|
||||||
|
|
||||||
# Write the session data cookie if it was loaded and has changed.
|
|
||||||
def close
|
|
||||||
if defined?(@data) && !@data.blank?
|
|
||||||
updated = marshal(@data)
|
|
||||||
raise CookieOverflow if updated.size > MAX
|
|
||||||
write_cookie('value' => updated) unless updated == @original
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Delete the session data by setting an expired cookie with no data.
|
def persistent_session_id!(data)
|
||||||
def delete
|
(data ||= {}).merge!(inject_persistent_session_id(data))
|
||||||
@data = nil
|
|
||||||
clear_old_cookie_value
|
|
||||||
write_cookie('value' => nil, 'expires' => 1.year.ago)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generate the HMAC keyed message digest. Uses SHA1 by default.
|
def inject_persistent_session_id(data)
|
||||||
def generate_digest(data)
|
requires_session_id?(data) ? { :session_id => generate_sid } : {}
|
||||||
key = @secret.respond_to?(:call) ? @secret.call(@session) : @secret
|
|
||||||
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), key, data)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def requires_session_id?(data)
|
||||||
# Marshal a session hash into safe cookie data. Include an integrity hash.
|
if data
|
||||||
def marshal(session)
|
data.respond_to?(:key?) && !data.key?(:session_id)
|
||||||
data = ActiveSupport::Base64.encode64s(Marshal.dump(session))
|
else
|
||||||
"#{data}--#{generate_digest(data)}"
|
true
|
||||||
end
|
|
||||||
|
|
||||||
# Unmarshal cookie data to a hash and verify its integrity.
|
|
||||||
def unmarshal(cookie)
|
|
||||||
if cookie
|
|
||||||
data, digest = cookie.split('--')
|
|
||||||
|
|
||||||
# Do two checks to transparently support old double-escaped data.
|
|
||||||
unless digest == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data))
|
|
||||||
delete
|
|
||||||
raise TamperedWithCookie
|
|
||||||
end
|
|
||||||
|
|
||||||
Marshal.load(ActiveSupport::Base64.decode64(data))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Read the session data cookie.
|
|
||||||
def read_cookie
|
|
||||||
@session.cgi.cookies[@cookie_options['name']].first
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# CGI likes to make you hack.
|
|
||||||
def write_cookie(options)
|
|
||||||
cookie = CGI::Cookie.new(@cookie_options.merge(options))
|
|
||||||
@session.cgi.send :instance_variable_set, '@output_cookies', [cookie]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Clear cookie value so subsequent new_session doesn't reload old data.
|
|
||||||
def clear_old_cookie_value
|
|
||||||
@session.cgi.cookies[@cookie_options['name']].clear
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
# This is a really simple session storage daemon, basically just a hash,
|
|
||||||
# which is enabled for DRb access.
|
|
||||||
|
|
||||||
require 'drb'
|
|
||||||
|
|
||||||
session_hash = Hash.new
|
|
||||||
session_hash.instance_eval { @mutex = Mutex.new }
|
|
||||||
|
|
||||||
class <<session_hash
|
|
||||||
def []=(key, value)
|
|
||||||
@mutex.synchronize do
|
|
||||||
super(key, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def [](key)
|
|
||||||
@mutex.synchronize do
|
|
||||||
super(key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(key)
|
|
||||||
@mutex.synchronize do
|
|
||||||
super(key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
DRb.start_service('druby://127.0.0.1:9192', session_hash)
|
|
||||||
DRb.thread.join
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
require 'cgi'
|
|
||||||
require 'cgi/session'
|
|
||||||
require 'drb'
|
|
||||||
|
|
||||||
class CGI #:nodoc:all
|
|
||||||
class Session
|
|
||||||
class DRbStore
|
|
||||||
@@session_data = DRbObject.new(nil, 'druby://localhost:9192')
|
|
||||||
|
|
||||||
def initialize(session, option=nil)
|
|
||||||
@session_id = session.session_id
|
|
||||||
end
|
|
||||||
|
|
||||||
def restore
|
|
||||||
@h = @@session_data[@session_id] || {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
@@session_data[@session_id] = @h
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
update
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete
|
|
||||||
@@session_data.delete(@session_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def data
|
|
||||||
@@session_data[@session_id]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,95 +1,48 @@
|
||||||
# cgi/session/memcached.rb - persistent storage of marshalled session data
|
|
||||||
#
|
|
||||||
# == Overview
|
|
||||||
#
|
|
||||||
# This file provides the CGI::Session::MemCache class, which builds
|
|
||||||
# persistence of storage data on top of the MemCache library. See
|
|
||||||
# cgi/session.rb for more details on session storage managers.
|
|
||||||
#
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
require 'cgi/session'
|
|
||||||
require_library_or_gem 'memcache'
|
require_library_or_gem 'memcache'
|
||||||
|
|
||||||
class CGI
|
module ActionController
|
||||||
class Session
|
module Session
|
||||||
# MemCache-based session storage class.
|
class MemCacheStore < AbstractStore
|
||||||
#
|
def initialize(app, options = {})
|
||||||
# This builds upon the top-level MemCache class provided by the
|
# Support old :expires option
|
||||||
# library file memcache.rb. Session data is marshalled and stored
|
options[:expire_after] ||= options[:expires]
|
||||||
# in a memcached cache.
|
|
||||||
class MemCacheStore
|
super
|
||||||
def check_id(id) #:nodoc:#
|
|
||||||
/[^0-9a-zA-Z]+/ =~ id.to_s ? false : true
|
@default_options = {
|
||||||
|
:namespace => 'rack:session',
|
||||||
|
:memcache_server => 'localhost:11211'
|
||||||
|
}.merge(@default_options)
|
||||||
|
|
||||||
|
@pool = options[:cache] || MemCache.new(@default_options[:memcache_server], @default_options)
|
||||||
|
unless @pool.servers.any? { |s| s.alive? }
|
||||||
|
raise "#{self} unable to find server during initialization."
|
||||||
|
end
|
||||||
|
@mutex = Mutex.new
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create a new CGI::Session::MemCache instance
|
private
|
||||||
#
|
def get_session(env, sid)
|
||||||
# This constructor is used internally by CGI::Session. The
|
sid ||= generate_sid
|
||||||
# user does not generally need to call it directly.
|
begin
|
||||||
#
|
session = @pool.get(sid) || {}
|
||||||
# +session+ is the session for which this instance is being
|
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
||||||
# created. The session id must only contain alphanumeric
|
session = {}
|
||||||
# characters; automatically generated session ids observe
|
|
||||||
# this requirement.
|
|
||||||
#
|
|
||||||
# +options+ is a hash of options for the initializer. The
|
|
||||||
# following options are recognized:
|
|
||||||
#
|
|
||||||
# cache:: an instance of a MemCache client to use as the
|
|
||||||
# session cache.
|
|
||||||
#
|
|
||||||
# expires:: an expiry time value to use for session entries in
|
|
||||||
# the session cache. +expires+ is interpreted in seconds
|
|
||||||
# relative to the current time if it’s less than 60*60*24*30
|
|
||||||
# (30 days), or as an absolute Unix time (e.g., Time#to_i) if
|
|
||||||
# greater. If +expires+ is +0+, or not passed on +options+,
|
|
||||||
# the entry will never expire.
|
|
||||||
#
|
|
||||||
# This session's memcache entry will be created if it does
|
|
||||||
# not exist, or retrieved if it does.
|
|
||||||
def initialize(session, options = {})
|
|
||||||
id = session.session_id
|
|
||||||
unless check_id(id)
|
|
||||||
raise ArgumentError, "session_id '%s' is invalid" % id
|
|
||||||
end
|
|
||||||
@cache = options['cache'] || MemCache.new('localhost')
|
|
||||||
@expires = options['expires'] || 0
|
|
||||||
@session_key = "session:#{id}"
|
|
||||||
@session_data = {}
|
|
||||||
# Add this key to the store if haven't done so yet
|
|
||||||
unless @cache.get(@session_key)
|
|
||||||
@cache.add(@session_key, @session_data, @expires)
|
|
||||||
end
|
end
|
||||||
|
[sid, session]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Restore session state from the session's memcache entry.
|
def set_session(env, sid, session_data)
|
||||||
#
|
options = env['rack.session.options']
|
||||||
# Returns the session state as a hash.
|
expiry = options[:expire_after] || 0
|
||||||
def restore
|
@pool.set(sid, session_data, expiry)
|
||||||
@session_data = @cache[@session_key] || {}
|
return true
|
||||||
|
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Save session state to the session's memcache entry.
|
|
||||||
def update
|
|
||||||
@cache.set(@session_key, @session_data, @expires)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Update and close the session's memcache entry.
|
|
||||||
def close
|
|
||||||
update
|
|
||||||
end
|
|
||||||
|
|
||||||
# Delete the session's memcache entry.
|
|
||||||
def delete
|
|
||||||
@cache.delete(@session_key)
|
|
||||||
@session_data = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def data
|
|
||||||
@session_data
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,8 @@
|
||||||
require 'action_controller/session/cookie_store'
|
|
||||||
require 'action_controller/session/drb_store'
|
|
||||||
require 'action_controller/session/mem_cache_store'
|
|
||||||
if Object.const_defined?(:ActiveRecord)
|
|
||||||
require 'action_controller/session/active_record_store'
|
|
||||||
end
|
|
||||||
|
|
||||||
module ActionController #:nodoc:
|
module ActionController #:nodoc:
|
||||||
module SessionManagement #:nodoc:
|
module SessionManagement #:nodoc:
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
base.class_eval do
|
base.class_eval do
|
||||||
extend ClassMethods
|
extend ClassMethods
|
||||||
alias_method_chain :process, :session_management_support
|
|
||||||
alias_method_chain :process_cleanup, :session_management_support
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -19,143 +10,44 @@ module ActionController #:nodoc:
|
||||||
# Set the session store to be used for keeping the session data between requests.
|
# Set the session store to be used for keeping the session data between requests.
|
||||||
# By default, sessions are stored in browser cookies (<tt>:cookie_store</tt>),
|
# By default, sessions are stored in browser cookies (<tt>:cookie_store</tt>),
|
||||||
# but you can also specify one of the other included stores (<tt>:active_record_store</tt>,
|
# but you can also specify one of the other included stores (<tt>:active_record_store</tt>,
|
||||||
# <tt>:p_store</tt>, <tt>:drb_store</tt>, <tt>:mem_cache_store</tt>, or
|
# <tt>:mem_cache_store</tt>, or your own custom class.
|
||||||
# <tt>:memory_store</tt>) or your own custom class.
|
|
||||||
def session_store=(store)
|
def session_store=(store)
|
||||||
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] =
|
if store == :active_record_store
|
||||||
store.is_a?(Symbol) ? CGI::Session.const_get(store == :drb_store ? "DRbStore" : store.to_s.camelize) : store
|
self.session_store = ActiveRecord::SessionStore
|
||||||
|
else
|
||||||
|
@@session_store = store.is_a?(Symbol) ?
|
||||||
|
Session.const_get(store.to_s.camelize) :
|
||||||
|
store
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the session store class currently used.
|
# Returns the session store class currently used.
|
||||||
def session_store
|
def session_store
|
||||||
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager]
|
if defined? @@session_store
|
||||||
|
@@session_store
|
||||||
|
else
|
||||||
|
Session::CookieStore
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def session=(options = {})
|
||||||
|
self.session_store = nil if options.delete(:disabled)
|
||||||
|
session_options.merge!(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the hash used to configure the session. Example use:
|
# Returns the hash used to configure the session. Example use:
|
||||||
#
|
#
|
||||||
# ActionController::Base.session_options[:session_secure] = true # session only available over HTTPS
|
# ActionController::Base.session_options[:secure] = true # session only available over HTTPS
|
||||||
def session_options
|
def session_options
|
||||||
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
|
@session_options ||= {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Specify how sessions ought to be managed for a subset of the actions on
|
|
||||||
# the controller. Like filters, you can specify <tt>:only</tt> and
|
|
||||||
# <tt>:except</tt> clauses to restrict the subset, otherwise options
|
|
||||||
# apply to all actions on this controller.
|
|
||||||
#
|
|
||||||
# The session options are inheritable, as well, so if you specify them in
|
|
||||||
# a parent controller, they apply to controllers that extend the parent.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
#
|
|
||||||
# # turn off session management for all actions.
|
|
||||||
# session :off
|
|
||||||
#
|
|
||||||
# # turn off session management for all actions _except_ foo and bar.
|
|
||||||
# session :off, :except => %w(foo bar)
|
|
||||||
#
|
|
||||||
# # turn off session management for only the foo and bar actions.
|
|
||||||
# session :off, :only => %w(foo bar)
|
|
||||||
#
|
|
||||||
# # the session will only work over HTTPS, but only for the foo action
|
|
||||||
# session :only => :foo, :session_secure => true
|
|
||||||
#
|
|
||||||
# # the session by default uses HttpOnly sessions for security reasons.
|
|
||||||
# # this can be switched off.
|
|
||||||
# session :only => :foo, :session_http_only => false
|
|
||||||
#
|
|
||||||
# # the session will only be disabled for 'foo', and only if it is
|
|
||||||
# # requested as a web service
|
|
||||||
# session :off, :only => :foo,
|
|
||||||
# :if => Proc.new { |req| req.parameters[:ws] }
|
|
||||||
#
|
|
||||||
# # the session will be disabled for non html/ajax requests
|
|
||||||
# session :off,
|
|
||||||
# :if => Proc.new { |req| !(req.format.html? || req.format.js?) }
|
|
||||||
#
|
|
||||||
# # turn the session back on, useful when it was turned off in the
|
|
||||||
# # application controller, and you need it on in another controller
|
|
||||||
# session :on
|
|
||||||
#
|
|
||||||
# All session options described for ActionController::Base.process_cgi
|
|
||||||
# are valid arguments.
|
|
||||||
def session(*args)
|
def session(*args)
|
||||||
options = args.extract_options!
|
ActiveSupport::Deprecation.warn(
|
||||||
|
"Disabling sessions for a single controller has been deprecated. " +
|
||||||
options[:disabled] = false if args.delete(:on)
|
"Sessions are now lazy loaded. So if you don't access them, " +
|
||||||
options[:disabled] = true if !args.empty?
|
"consider them off. You can still modify the session cookie " +
|
||||||
options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only]
|
"options with request.session_options.", caller)
|
||||||
options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except]
|
|
||||||
if options[:only] && options[:except]
|
|
||||||
raise ArgumentError, "only one of either :only or :except are allowed"
|
|
||||||
end
|
|
||||||
|
|
||||||
write_inheritable_array(:session_options, [options])
|
|
||||||
end
|
|
||||||
|
|
||||||
# So we can declare session options in the Rails initializer.
|
|
||||||
alias_method :session=, :session
|
|
||||||
|
|
||||||
def cached_session_options #:nodoc:
|
|
||||||
@session_options ||= read_inheritable_attribute(:session_options) || []
|
|
||||||
end
|
|
||||||
|
|
||||||
def session_options_for(request, action) #:nodoc:
|
|
||||||
if (session_options = cached_session_options).empty?
|
|
||||||
{}
|
|
||||||
else
|
|
||||||
options = {}
|
|
||||||
|
|
||||||
action = action.to_s
|
|
||||||
session_options.each do |opts|
|
|
||||||
next if opts[:if] && !opts[:if].call(request)
|
|
||||||
if opts[:only] && opts[:only].include?(action)
|
|
||||||
options.merge!(opts)
|
|
||||||
elsif opts[:except] && !opts[:except].include?(action)
|
|
||||||
options.merge!(opts)
|
|
||||||
elsif !opts[:only] && !opts[:except]
|
|
||||||
options.merge!(opts)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if options.empty? then options
|
|
||||||
else
|
|
||||||
options.delete :only
|
|
||||||
options.delete :except
|
|
||||||
options.delete :if
|
|
||||||
options[:disabled] ? false : options
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_with_session_management_support(request, response, method = :perform_action, *arguments) #:nodoc:
|
|
||||||
set_session_options(request)
|
|
||||||
process_without_session_management_support(request, response, method, *arguments)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def set_session_options(request)
|
|
||||||
request.session_options = self.class.session_options_for(request, request.parameters["action"] || "index")
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_cleanup_with_session_management_support
|
|
||||||
clear_persistent_model_associations
|
|
||||||
process_cleanup_without_session_management_support
|
|
||||||
end
|
|
||||||
|
|
||||||
# Clear cached associations in session data so they don't overflow
|
|
||||||
# the database field. Only applies to ActiveRecordStore since there
|
|
||||||
# is not a standard way to iterate over session data.
|
|
||||||
def clear_persistent_model_associations #:doc:
|
|
||||||
if defined?(@_session) && @_session.respond_to?(:data)
|
|
||||||
session_data = @_session.data
|
|
||||||
|
|
||||||
if session_data && session_data.respond_to?(:each_value)
|
|
||||||
session_data.each_value do |obj|
|
|
||||||
obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
require 'active_support/core_ext/string/bytesize'
|
||||||
|
|
||||||
module ActionController #:nodoc:
|
module ActionController #:nodoc:
|
||||||
# Methods for sending files and streams to the browser instead of rendering.
|
# Methods for sending arbitrary data and for streaming files to the browser,
|
||||||
|
# instead of rendering.
|
||||||
module Streaming
|
module Streaming
|
||||||
DEFAULT_SEND_FILE_OPTIONS = {
|
DEFAULT_SEND_FILE_OPTIONS = {
|
||||||
:type => 'application/octet-stream'.freeze,
|
:type => 'application/octet-stream'.freeze,
|
||||||
|
|
@ -24,7 +27,8 @@ module ActionController #:nodoc:
|
||||||
# Options:
|
# Options:
|
||||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
||||||
# Defaults to <tt>File.basename(path)</tt>.
|
# Defaults to <tt>File.basename(path)</tt>.
|
||||||
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
|
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
|
||||||
|
# either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
|
||||||
# * <tt>:length</tt> - used to manually override the length (in bytes) of the content that
|
# * <tt>:length</tt> - used to manually override the length (in bytes) of the content that
|
||||||
# is going to be sent to the client. Defaults to <tt>File.size(path)</tt>.
|
# is going to be sent to the client. Defaults to <tt>File.size(path)</tt>.
|
||||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||||
|
|
@ -102,12 +106,16 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Send binary data to the user as a file download. May set content type, apparent file name,
|
# Sends the given binary data to the browser. This method is similar to
|
||||||
# and specify whether to show data inline or download as an attachment.
|
# <tt>render :text => data</tt>, but also allows you to specify whether
|
||||||
|
# the browser should display the response as a file attachment (i.e. in a
|
||||||
|
# download dialog) or as inline data. You may also set the content type,
|
||||||
|
# the apparent file name, and other things.
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
||||||
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'.
|
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify
|
||||||
|
# either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json
|
||||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
||||||
# Valid values are 'inline' and 'attachment' (default).
|
# Valid values are 'inline' and 'attachment' (default).
|
||||||
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
|
||||||
|
|
@ -125,9 +133,13 @@ module ActionController #:nodoc:
|
||||||
# send_data image.data, :type => image.content_type, :disposition => 'inline'
|
# send_data image.data, :type => image.content_type, :disposition => 'inline'
|
||||||
#
|
#
|
||||||
# See +send_file+ for more information on HTTP Content-* headers and caching.
|
# See +send_file+ for more information on HTTP Content-* headers and caching.
|
||||||
|
#
|
||||||
|
# <b>Tip:</b> if you want to stream large amounts of on-the-fly generated
|
||||||
|
# data to the browser, then use <tt>render :text => proc { ... }</tt>
|
||||||
|
# instead. See ActionController::Base#render for more information.
|
||||||
def send_data(data, options = {}) #:doc:
|
def send_data(data, options = {}) #:doc:
|
||||||
logger.info "Sending data #{options[:filename]}" if logger
|
logger.info "Sending data #{options[:filename]}" if logger
|
||||||
send_file_headers! options.merge(:length => data.size)
|
send_file_headers! options.merge(:length => data.bytesize)
|
||||||
@performed_render = false
|
@performed_render = false
|
||||||
render :status => options[:status], :text => data
|
render :status => options[:status], :text => data
|
||||||
end
|
end
|
||||||
|
|
@ -143,9 +155,16 @@ module ActionController #:nodoc:
|
||||||
|
|
||||||
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
|
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
|
||||||
|
|
||||||
headers.update(
|
content_type = options[:type]
|
||||||
'Content-Length' => options[:length],
|
if content_type.is_a?(Symbol)
|
||||||
'Content-Type' => options[:type].to_s.strip, # fixes a problem with extra '\r' with some browsers
|
raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.has_key?(content_type.to_s)
|
||||||
|
content_type = Mime::Type.lookup_by_extension(content_type.to_s)
|
||||||
|
end
|
||||||
|
content_type = content_type.to_s.strip # fixes a problem with extra '\r' with some browsers
|
||||||
|
|
||||||
|
headers.merge!(
|
||||||
|
'Content-Length' => options[:length].to_s,
|
||||||
|
'Content-Type' => content_type,
|
||||||
'Content-Disposition' => disposition,
|
'Content-Disposition' => disposition,
|
||||||
'Content-Transfer-Encoding' => 'binary'
|
'Content-Transfer-Encoding' => 'binary'
|
||||||
)
|
)
|
||||||
|
|
|
||||||
29
vendor/rails/actionpack/lib/action_controller/string_coercion.rb
vendored
Normal file
29
vendor/rails/actionpack/lib/action_controller/string_coercion.rb
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
module ActionController
|
||||||
|
class StringCoercion
|
||||||
|
class UglyBody < ActiveSupport::BasicObject
|
||||||
|
def initialize(body)
|
||||||
|
@body = body
|
||||||
|
end
|
||||||
|
|
||||||
|
def each
|
||||||
|
@body.each do |part|
|
||||||
|
yield part.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def method_missing(*args, &block)
|
||||||
|
@body.__send__(*args, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(app)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
status, headers, body = @app.call(env)
|
||||||
|
[status, headers, UglyBody.new(body)]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -6,6 +6,6 @@
|
||||||
</h1>
|
</h1>
|
||||||
<pre><%=h @exception.clean_message %></pre>
|
<pre><%=h @exception.clean_message %></pre>
|
||||||
|
|
||||||
<%= render(:file => @rescues_path + "/_trace.erb") %>
|
<%= render :file => @rescues_path["rescues/_trace.erb"] %>
|
||||||
|
|
||||||
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
|
<%= render :file => @rescues_path["rescues/_request_and_response.erb"] %>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<% @real_exception = @exception
|
<% @real_exception = @exception
|
||||||
@exception = @exception.original_exception || @exception %>
|
@exception = @exception.original_exception || @exception %>
|
||||||
<%= render(:file => @rescues_path + "/_trace.erb") %>
|
<%= render :file => @rescues_path["rescues/_trace.erb"] %>
|
||||||
<% @exception = @real_exception %>
|
<% @exception = @real_exception %>
|
||||||
|
|
||||||
<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
|
<%= render :file => @rescues_path["rescues/_request_and_response.erb"] %>
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,7 @@
|
||||||
require 'active_support/test_case'
|
require 'active_support/test_case'
|
||||||
|
require 'action_controller/test_process'
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
class NonInferrableControllerError < ActionControllerError
|
|
||||||
def initialize(name)
|
|
||||||
@name = name
|
|
||||||
super "Unable to determine the controller to test from #{name}. " +
|
|
||||||
"You'll need to specify it using 'tests YourController' in your " +
|
|
||||||
"test case definition. This could mean that #{inferred_controller_name} does not exist " +
|
|
||||||
"or it contains syntax errors"
|
|
||||||
end
|
|
||||||
|
|
||||||
def inferred_controller_name
|
|
||||||
@name.sub(/Test$/, '')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Superclass for ActionController functional tests. Functional tests allow you to
|
# Superclass for ActionController functional tests. Functional tests allow you to
|
||||||
# test a single controller action per test method. This should not be confused with
|
# test a single controller action per test method. This should not be confused with
|
||||||
# integration tests (see ActionController::IntegrationTest), which are more like
|
# integration tests (see ActionController::IntegrationTest), which are more like
|
||||||
|
|
@ -74,7 +61,70 @@ module ActionController
|
||||||
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
|
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
|
||||||
# tests WidgetController
|
# tests WidgetController
|
||||||
# end
|
# end
|
||||||
|
#
|
||||||
|
# == Testing controller internals
|
||||||
|
#
|
||||||
|
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
|
||||||
|
# can be used against. These collections are:
|
||||||
|
#
|
||||||
|
# * assigns: Instance variables assigned in the action that are available for the view.
|
||||||
|
# * session: Objects being saved in the session.
|
||||||
|
# * flash: The flash objects currently in the session.
|
||||||
|
# * cookies: Cookies being sent to the user on this request.
|
||||||
|
#
|
||||||
|
# These collections can be used just like any other hash:
|
||||||
|
#
|
||||||
|
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
|
||||||
|
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
|
||||||
|
# assert flash.empty? # makes sure that there's nothing in the flash
|
||||||
|
#
|
||||||
|
# For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
|
||||||
|
# appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
|
||||||
|
# So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
|
||||||
|
#
|
||||||
|
# On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
|
||||||
|
#
|
||||||
|
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
|
||||||
|
# action call which can then be asserted against.
|
||||||
|
#
|
||||||
|
# == Manipulating the request collections
|
||||||
|
#
|
||||||
|
# The collections described above link to the response, so you can test if what the actions were expected to do happened. But
|
||||||
|
# sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
|
||||||
|
# and cookies, though. For sessions, you just do:
|
||||||
|
#
|
||||||
|
# @request.session[:key] = "value"
|
||||||
|
# @request.cookies["key"] = "value"
|
||||||
|
#
|
||||||
|
# == Testing named routes
|
||||||
|
#
|
||||||
|
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# assert_redirected_to page_url(:title => 'foo')
|
||||||
class TestCase < ActiveSupport::TestCase
|
class TestCase < ActiveSupport::TestCase
|
||||||
|
include TestProcess
|
||||||
|
|
||||||
|
def initialize(*args)
|
||||||
|
super
|
||||||
|
@controller = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
module Assertions
|
||||||
|
%w(response selector tag dom routing model).each do |kind|
|
||||||
|
include ActionController::Assertions.const_get("#{kind.camelize}Assertions")
|
||||||
|
end
|
||||||
|
|
||||||
|
def clean_backtrace(&block)
|
||||||
|
yield
|
||||||
|
rescue ActiveSupport::TestCase::Assertion => error
|
||||||
|
framework_path = Regexp.new(File.expand_path("#{File.dirname(__FILE__)}/assertions"))
|
||||||
|
error.backtrace.reject! { |line| File.expand_path(line) =~ framework_path }
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end
|
||||||
|
include Assertions
|
||||||
|
|
||||||
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
|
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
|
||||||
# (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
|
# (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
|
||||||
# rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
|
# rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
|
||||||
|
|
@ -82,8 +132,14 @@ module ActionController
|
||||||
#
|
#
|
||||||
# The exception is stored in the exception accessor for further inspection.
|
# The exception is stored in the exception accessor for further inspection.
|
||||||
module RaiseActionExceptions
|
module RaiseActionExceptions
|
||||||
|
def self.included(base)
|
||||||
|
base.class_eval do
|
||||||
attr_accessor :exception
|
attr_accessor :exception
|
||||||
|
protected :exception, :exception=
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
def rescue_action_without_handler(e)
|
def rescue_action_without_handler(e)
|
||||||
self.exception = e
|
self.exception = e
|
||||||
|
|
||||||
|
|
@ -107,7 +163,7 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def controller_class=(new_class)
|
def controller_class=(new_class)
|
||||||
prepare_controller_class(new_class)
|
prepare_controller_class(new_class) if new_class
|
||||||
write_inheritable_attribute(:controller_class, new_class)
|
write_inheritable_attribute(:controller_class, new_class)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -122,7 +178,7 @@ module ActionController
|
||||||
def determine_default_controller_class(name)
|
def determine_default_controller_class(name)
|
||||||
name.sub(/Test$/, '').constantize
|
name.sub(/Test$/, '').constantize
|
||||||
rescue NameError
|
rescue NameError
|
||||||
raise NonInferrableControllerError.new(name)
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_controller_class(new_class)
|
def prepare_controller_class(new_class)
|
||||||
|
|
@ -131,13 +187,19 @@ module ActionController
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_controller_request_and_response
|
def setup_controller_request_and_response
|
||||||
@controller = self.class.controller_class.new
|
@request = TestRequest.new
|
||||||
@controller.request = @request = TestRequest.new
|
|
||||||
@response = TestResponse.new
|
@response = TestResponse.new
|
||||||
|
|
||||||
|
if klass = self.class.controller_class
|
||||||
|
@controller ||= klass.new rescue nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if @controller
|
||||||
|
@controller.request = @request
|
||||||
@controller.params = {}
|
@controller.params = {}
|
||||||
@controller.send(:initialize_current_url)
|
@controller.send(:initialize_current_url)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
|
# Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
|
||||||
def rescue_action_in_public!
|
def rescue_action_in_public!
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue