aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/metal
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller/metal')
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb147
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb118
-rw-r--r--actionpack/lib/action_controller/metal/cookies.rb94
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb58
-rw-r--r--actionpack/lib/action_controller/metal/filter_parameter_logging.rb96
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb164
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb176
-rw-r--r--actionpack/lib/action_controller/metal/hide_actions.rb35
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb309
-rw-r--r--actionpack/lib/action_controller/metal/layouts.rb192
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb353
-rw-r--r--actionpack/lib/action_controller/metal/rack_convenience.rb33
-rw-r--r--actionpack/lib/action_controller/metal/redirector.rb19
-rw-r--r--actionpack/lib/action_controller/metal/render_options.rb103
-rw-r--r--actionpack/lib/action_controller/metal/renderer.rb77
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb118
-rw-r--r--actionpack/lib/action_controller/metal/rescuable.rb52
-rw-r--r--actionpack/lib/action_controller/metal/session.rb15
-rw-r--r--actionpack/lib/action_controller/metal/session_management.rb54
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb187
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb39
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb49
-rw-r--r--actionpack/lib/action_controller/metal/verification.rb130
23 files changed, 2618 insertions, 0 deletions
diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
new file mode 100644
index 0000000000..23e7b1b3af
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/compatibility.rb
@@ -0,0 +1,147 @@
+module ActionController
+ module Rails2Compatibility
+ extend ActiveSupport::Concern
+
+ class ::ActionController::ActionControllerError < StandardError #:nodoc:
+ end
+
+ # Temporary hax
+ included do
+ ::ActionController::UnknownAction = ::AbstractController::ActionNotFound
+ ::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError
+
+ cattr_accessor :session_options
+ self.session_options = {}
+
+ cattr_accessor :allow_concurrency
+ self.allow_concurrency = false
+
+ cattr_accessor :param_parsers
+ self.param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
+ Mime::URL_ENCODED_FORM => :url_encoded_form,
+ Mime::XML => :xml_simple,
+ Mime::JSON => :json }
+
+ cattr_accessor :relative_url_root
+ self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
+
+ cattr_accessor :default_charset
+ self.default_charset = "utf-8"
+
+ # cattr_reader :protected_instance_variables
+ cattr_accessor :protected_instance_variables
+ self.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 @_headers @_params
+ @_flash @_response)
+
+ # Indicates whether or not optimise the generated named
+ # route helper methods
+ cattr_accessor :optimise_named_routes
+ self.optimise_named_routes = true
+
+ cattr_accessor :resources_path_names
+ self.resources_path_names = { :new => 'new', :edit => 'edit' }
+
+ # Controls the resource action separator
+ cattr_accessor :resource_action_separator
+ self.resource_action_separator = "/"
+
+ cattr_accessor :use_accept_header
+ self.use_accept_header = true
+
+ cattr_accessor :page_cache_directory
+ self.page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
+
+ cattr_reader :cache_store
+
+ cattr_accessor :consider_all_requests_local
+ self.consider_all_requests_local = true
+
+ # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
+ # and images to a dedicated asset server away from the main web server. Example:
+ # ActionController::Base.asset_host = "http://assets.example.com"
+ cattr_accessor :asset_host
+
+ cattr_accessor :ip_spoofing_check
+ self.ip_spoofing_check = true
+ end
+
+ # For old tests
+ def initialize_template_class(*) end
+ def assign_shortcuts(*) end
+
+ # TODO: Remove this after we flip
+ def template
+ @template ||= view_context
+ end
+
+ def process_action(*)
+ template
+ super
+ end
+
+ module ClassMethods
+ def consider_all_requests_local
+ end
+
+ def rescue_action(env)
+ raise env["action_dispatch.rescue.exception"]
+ end
+
+ # Defines the storage option for cached fragments
+ def cache_store=(store_option)
+ @@cache_store = ActiveSupport::Cache.lookup_store(store_option)
+ end
+ end
+
+ def render_to_body(options)
+ if options.is_a?(Hash) && options.key?(:template)
+ options[:template].sub!(/^\//, '')
+ end
+
+ options[:text] = nil if options[:nothing] == true
+
+ body = super
+ body = [' '] if body.blank?
+ body
+ end
+
+ def _handle_method_missing
+ method_missing(@_action_name.to_sym)
+ end
+
+ def method_for_action(action_name)
+ super || (respond_to?(:method_missing) && "_handle_method_missing")
+ end
+
+ def _find_layout(name, details)
+ details[:prefix] = nil if name =~ /\blayouts/
+ super
+ end
+
+ # Move this into a "don't run in production" module
+ def _default_layout(details, require_layout = false)
+ super
+ rescue ActionView::MissingTemplate
+ _find_layout(_layout({}), {})
+ nil
+ end
+
+ def performed?
+ response_body
+ end
+
+ # ==== Request only view path switching ====
+ def append_view_path(path)
+ view_paths.push(*path)
+ end
+
+ def prepend_view_path(path)
+ view_paths.unshift(*path)
+ end
+
+ def view_paths
+ view_context.view_paths
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
new file mode 100644
index 0000000000..6d35137428
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -0,0 +1,118 @@
+module ActionController
+ module ConditionalGet
+ extend ActiveSupport::Concern
+
+ include RackConvenience
+
+ # Sets the etag, last_modified, or both on the response and renders a
+ # "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:
+ #
+ # def show
+ # @article = Article.find(params[:id])
+ # fresh_when(:etag => @article, :last_modified => @article.created_at.utc, :public => true)
+ # end
+ #
+ # 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.
+ #
+ def fresh_when(options)
+ options.assert_valid_keys(:etag, :last_modified, :public)
+
+ response.etag = options[:etag] if options[:etag]
+ response.last_modified = options[:last_modified] if options[:last_modified]
+
+ if options[:public]
+ response.cache_control[:public] = true
+ end
+
+ if request.fresh?(response)
+ head :not_modified
+ end
+ end
+
+ # Return a response that has no content (merely headers). The options
+ # argument is interpreted to be a hash of header names and values.
+ # This allows you to easily return a response that consists only of
+ # significant headers:
+ #
+ # head :created, :location => person_path(@person)
+ #
+ # It can also be used to return exceptional conditions:
+ #
+ # return head(:method_not_allowed) unless request.post?
+ # return head(:bad_request) unless valid_request?
+ # render
+ def head(*args)
+ if args.length > 2
+ raise ArgumentError, "too many arguments to head"
+ elsif args.empty?
+ raise ArgumentError, "too few arguments to head"
+ end
+ options = args.extract_options!
+ status = args.shift || options.delete(:status) || :ok
+
+ options.each do |key, value|
+ headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s
+ end
+
+ render :nothing => true, :status => status
+ end
+
+ # Sets the etag and/or last_modified on the response and checks it against
+ # the client request. If the request doesn't match the options provided, the
+ # 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.
+ #
+ # 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:
+ #
+ # def show
+ # @article = Article.find(params[:id])
+ #
+ # if stale?(:etag => @article, :last_modified => @article.created_at.utc)
+ # @statistics = @article.really_expensive_call
+ # respond_to do |format|
+ # # all the supported formats
+ # end
+ # end
+ # end
+ def stale?(options)
+ fresh_when(options)
+ !request.fresh?(response)
+ end
+
+ # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that
+ # intermediate caches shouldn't cache the response.
+ #
+ # Examples:
+ # expires_in 20.minutes
+ # expires_in 3.hours, :public => true
+ # expires in 3.hours, 'max-stale' => 5.hours, :public => true
+ #
+ # This method will overwrite an existing Cache-Control header.
+ # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
+ def expires_in(seconds, options = {}) #:doc:
+ response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public))
+ options.delete(:private)
+
+ response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
+ end
+
+ # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or
+ # intermediate caches (like caching proxy servers).
+ def expires_now #:doc:
+ response.headers["Cache-Control"] = "no-cache"
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb
new file mode 100644
index 0000000000..d4806623c3
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/cookies.rb
@@ -0,0 +1,94 @@
+module ActionController #:nodoc:
+ # Cookies are read and written through ActionController#cookies.
+ #
+ # The cookies being read are the ones received along with the request, the cookies
+ # being written will be sent out with the response. Reading a cookie does not get
+ # the cookie object itself back, just the value it holds.
+ #
+ # Examples for writing:
+ #
+ # # Sets a simple session cookie.
+ # cookies[:user_name] = "david"
+ #
+ # # Sets a cookie that expires in 1 hour.
+ # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
+ #
+ # Examples for reading:
+ #
+ # cookies[:user_name] # => "david"
+ # cookies.size # => 2
+ #
+ # Example for deleting:
+ #
+ # cookies.delete :user_name
+ #
+ # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
+ #
+ # cookies[:key] = {
+ # :value => 'a yummy cookie',
+ # :expires => 1.year.from_now,
+ # :domain => 'domain.com'
+ # }
+ #
+ # cookies.delete(:key, :domain => 'domain.com')
+ #
+ # The option symbols for setting cookies are:
+ #
+ # * <tt>:value</tt> - The cookie's value or list of values (as an array).
+ # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
+ # of the application.
+ # * <tt>:domain</tt> - The domain for which this cookie applies.
+ # * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
+ # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
+ # Default is +false+.
+ # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
+ # only HTTP. Defaults to +false+.
+ module Cookies
+ def self.included(base)
+ base.helper_method :cookies
+ end
+
+ protected
+ # Returns the cookie container, which operates as described above.
+ def cookies
+ @cookies ||= CookieJar.new(self)
+ end
+ end
+
+ class CookieJar < Hash #:nodoc:
+ def initialize(controller)
+ @controller, @cookies = controller, controller.request.cookies
+ super()
+ update(@cookies)
+ end
+
+ # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
+ def [](name)
+ super(name.to_s)
+ end
+
+ # Sets the cookie named +name+. The second argument may be the very cookie
+ # value, or a hash of options as documented above.
+ def []=(key, options)
+ if options.is_a?(Hash)
+ options.symbolize_keys!
+ else
+ options = { :value => options }
+ end
+
+ options[:path] = "/" unless options.has_key?(:path)
+ super(key.to_s, options[:value])
+ @controller.response.set_cookie(key, options)
+ end
+
+ # Removes the cookie on the client machine by setting the value to an empty string
+ # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
+ # an options hash to delete cookies with extra data such as a <tt>:path</tt>.
+ def delete(key, options = {})
+ options.symbolize_keys!
+ options[:path] = "/" unless options.has_key?(:path)
+ super(key.to_s)
+ @controller.response.delete_cookie(key, options)
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
new file mode 100644
index 0000000000..d0811254cb
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -0,0 +1,58 @@
+module ActionController
+ class ActionControllerError < StandardError #:nodoc:
+ end
+
+ class SessionRestoreError < ActionControllerError #:nodoc:
+ end
+
+ class RenderError < ActionControllerError #:nodoc:
+ end
+
+ class RoutingError < ActionControllerError #:nodoc:
+ attr_reader :failures
+ def initialize(message, failures=[])
+ super(message)
+ @failures = failures
+ end
+ end
+
+ class MethodNotAllowed < ActionControllerError #:nodoc:
+ attr_reader :allowed_methods
+
+ def initialize(*allowed_methods)
+ super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
+ @allowed_methods = allowed_methods
+ end
+
+ def allowed_methods_header
+ allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', '
+ end
+
+ def handle_response!(response)
+ response.headers['Allow'] ||= allowed_methods_header
+ end
+ end
+
+ class NotImplemented < MethodNotAllowed #:nodoc:
+ end
+
+ class UnknownController < ActionControllerError #:nodoc:
+ end
+
+ class MissingFile < ActionControllerError #:nodoc:
+ end
+
+ class RenderError < ActionControllerError #:nodoc:
+ end
+
+ class SessionOverflowError < ActionControllerError #:nodoc:
+ DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
+
+ def initialize(message = nil)
+ super(message || DEFAULT_MESSAGE)
+ end
+ end
+
+ class UnknownHttpMethod < ActionControllerError #:nodoc:
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
new file mode 100644
index 0000000000..065e62a37f
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
@@ -0,0 +1,96 @@
+module ActionController
+ module FilterParameterLogging
+ extend ActiveSupport::Concern
+
+ include AbstractController::Logger
+
+ included do
+ include InstanceMethodsForNewBase
+ end
+
+ module ClassMethods
+ # Replace sensitive parameter data from the request log.
+ # Filters parameters that have any of the arguments as a substring.
+ # Looks in all subhashes of the param hash for keys to filter.
+ # If a block is given, each key and value of the parameter hash and all
+ # subhashes is passed to it, the value or key
+ # can be replaced using String#replace or similar method.
+ #
+ # Examples:
+ # filter_parameter_logging
+ # => Does nothing, just slows the logging process down
+ #
+ # filter_parameter_logging :password
+ # => replaces the value to all keys matching /password/i with "[FILTERED]"
+ #
+ # filter_parameter_logging :foo, "bar"
+ # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
+ #
+ # filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i }
+ # => reverses the value to all keys matching /secret/i
+ #
+ # filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i }
+ # => reverses the value to all keys matching /secret/i, and
+ # replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
+ def filter_parameter_logging(*filter_words, &block)
+ parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0
+
+ define_method(:filter_parameters) do |unfiltered_parameters|
+ filtered_parameters = {}
+
+ unfiltered_parameters.each do |key, value|
+ if key =~ parameter_filter
+ filtered_parameters[key] = '[FILTERED]'
+ elsif value.is_a?(Hash)
+ filtered_parameters[key] = filter_parameters(value)
+ elsif value.is_a?(Array)
+ filtered_parameters[key] = value.collect do |item|
+ filter_parameters(item)
+ end
+ elsif block_given?
+ key = key.dup
+ value = value.dup if value
+ yield key, value
+ filtered_parameters[key] = value
+ else
+ filtered_parameters[key] = value
+ end
+ end
+
+ filtered_parameters
+ end
+ protected :filter_parameters
+ end
+ end
+
+ module InstanceMethodsForNewBase
+ # TODO : Fix the order of information inside such that it's exactly same as the old base
+ def process(*)
+ ret = super
+
+ if logger
+ parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
+ parameters = parameters.except!(:controller, :action, :format, :_method, :only_path)
+
+ unless parameters.empty?
+ # TODO : Move DelayedLog to AS
+ log = AbstractController::Logger::DelayedLog.new { " Parameters: #{parameters.inspect}" }
+ logger.info(log)
+ end
+ end
+
+ ret
+ end
+ end
+
+ private
+
+ # TODO : This method is not needed for the new base
+ def log_processing_for_parameters
+ parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup
+ parameters = parameters.except!(:controller, :action, :format, :_method)
+
+ logger.info " Parameters: #{parameters.inspect}" unless parameters.empty?
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
new file mode 100644
index 0000000000..590f9be3ac
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -0,0 +1,164 @@
+module ActionController #:nodoc:
+ # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
+ # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
+ # 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:
+ #
+ # class PostsController < ActionController::Base
+ # def create
+ # # save post
+ # flash[:notice] = "Successfully created post"
+ # redirect_to posts_path(@post)
+ # end
+ #
+ # def show
+ # # doesn't need to assign the flash notice to the template, that's done automatically
+ # end
+ # end
+ #
+ # show.html.erb
+ # <% 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
+ # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
+ #
+ # See docs on the FlashHash class for more details about the flash.
+ module Flash
+ extend ActiveSupport::Concern
+
+ include Session
+
+ class FlashNow #:nodoc:
+ def initialize(flash)
+ @flash = flash
+ end
+
+ def []=(k, v)
+ @flash[k] = v
+ @flash.discard(k)
+ v
+ end
+
+ def [](k)
+ @flash[k]
+ end
+ end
+
+ class FlashHash < Hash
+ def initialize #:nodoc:
+ super
+ @used = {}
+ end
+
+ def []=(k, v) #:nodoc:
+ keep(k)
+ super
+ end
+
+ def update(h) #:nodoc:
+ h.keys.each { |k| keep(k) }
+ super
+ end
+
+ alias :merge! :update
+
+ def replace(h) #:nodoc:
+ @used = {}
+ super
+ end
+
+ # Sets a flash that will not be available to the next action, only to the current.
+ #
+ # flash.now[:message] = "Hello current action"
+ #
+ # This method enables you to use the flash as a central messaging system in your app.
+ # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
+ # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
+ # vanish when the current action is done.
+ #
+ # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
+ def now
+ FlashNow.new(self)
+ end
+
+ # Keeps either the entire current flash or a specific flash entry available for the next action:
+ #
+ # flash.keep # keeps the entire flash
+ # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
+ def keep(k = nil)
+ use(k, false)
+ end
+
+ # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
+ #
+ # flash.discard # discard the entire flash at the end of the current action
+ # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
+ def discard(k = nil)
+ use(k)
+ end
+
+ # Mark for removal entries that were kept, and delete unkept ones.
+ #
+ # This method is called automatically by filters, so you generally don't need to care about it.
+ def sweep #:nodoc:
+ keys.each do |k|
+ unless @used[k]
+ use(k)
+ else
+ delete(k)
+ @used.delete(k)
+ end
+ end
+
+ # clean up after keys that could have been left over by calling reject! or shift on the flash
+ (@used.keys - keys).each{ |k| @used.delete(k) }
+ end
+
+ def store(session, key = "flash")
+ return if self.empty?
+ session[key] = self
+ end
+
+ private
+ # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
+ # use() # marks the entire flash as used
+ # use('msg') # marks the "msg" entry as used
+ # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
+ # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
+ # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
+ # if no key is passed.
+ def use(key = nil, used = true)
+ Array(key || keys).each { |k| @used[k] = used }
+ return key ? self[key] : self
+ end
+ end
+
+ protected
+ def process_action(method_name)
+ super
+ if defined? @_flash
+ @_flash.store(session)
+ remove_instance_variable(:@_flash)
+ end
+ end
+
+ def reset_session
+ super
+ remove_instance_variable(:@_flash) if defined?(@_flash)
+ 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
+
+ @_flash
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
new file mode 100644
index 0000000000..7c52779064
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -0,0 +1,176 @@
+require 'active_support/dependencies'
+
+module ActionController
+ # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+,
+ # +numbers+ and model objects, to name a few. These helpers are available to all templates
+ # by default.
+ #
+ # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to
+ # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will
+ # include a helper whose name matches that of the controller, e.g., <tt>MyController</tt> will automatically
+ # include <tt>MyHelper</tt>.
+ #
+ # Additional helpers can be specified using the +helper+ class method in <tt>ActionController::Base</tt> or any
+ # controller which inherits from it.
+ #
+ # ==== Examples
+ # The +to_s+ method from the Time class can be wrapped in a helper method to display a custom message if
+ # the Time object is blank:
+ #
+ # module FormattedTimeHelper
+ # def format_time(time, format=:long, blank_message="&nbsp;")
+ # time.blank? ? blank_message : time.to_s(format)
+ # end
+ # end
+ #
+ # FormattedTimeHelper can now be included in a controller, using the +helper+ class method:
+ #
+ # class EventsController < ActionController::Base
+ # helper FormattedTimeHelper
+ # def index
+ # @events = Event.find(:all)
+ # end
+ # end
+ #
+ # Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
+ #
+ # <% @events.each do |event| -%>
+ # <p>
+ # <% format_time(event.time, :short, "N/A") %> | <%= event.name %>
+ # </p>
+ # <% end -%>
+ #
+ # Finally, assuming we have two event instances, one which has a time and one which does not,
+ # the output might look like this:
+ #
+ # 23 Aug 11:30 | Carolina Railhawks Soccer Match
+ # N/A | Carolina Railhaws Training Workshop
+ #
+ module Helpers
+ extend ActiveSupport::Concern
+
+ include AbstractController::Helpers
+
+ included do
+ # Set the default directory for helpers
+ extlib_inheritable_accessor(:helpers_dir) do
+ defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers"
+ end
+ end
+
+ module ClassMethods
+ def inherited(klass)
+ klass.class_eval { default_helper_module! unless name.blank? }
+ super
+ end
+
+ # The +helper+ class method can take a series of helper module names, a block, or both.
+ #
+ # ==== Parameters
+ # *args<Array[Module, Symbol, String, :all]>
+ # block<Block>:: A block defining helper methods
+ #
+ # ==== Examples
+ # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file
+ # and include the module in the template class. The second form illustrates how to include custom helpers
+ # when working with namespaced controllers, or other cases where the file containing the helper definition is not
+ # in one of Rails' standard load paths:
+ # helper :foo # => requires 'foo_helper' and includes FooHelper
+ # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper
+ #
+ # When the argument is a module it will be included directly in the template class.
+ # helper FooHelper # => includes FooHelper
+ #
+ # When the argument is the symbol <tt>:all</tt>, the controller will include all helpers beneath
+ # <tt>ActionController::Base.helpers_dir</tt> (defaults to <tt>app/helpers/**/*.rb</tt> under RAILS_ROOT).
+ # helper :all
+ #
+ # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
+ # to the template.
+ # # One line
+ # helper { def hello() "Hello, world!" end }
+ # # Multi-line
+ # helper do
+ # def foo(bar)
+ # "#{bar} is the very best"
+ # end
+ # end
+ #
+ # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of
+ # +symbols+, +strings+, +modules+ and blocks.
+ # helper(:three, BlindHelper) { def mice() 'mice' end }
+ #
+ def helper(*args, &block)
+ super(*_modules_for_helpers(args), &block)
+ end
+
+ # Declares helper accessors for controller attributes. For example, the
+ # following adds new +name+ and <tt>name=</tt> instance methods to a
+ # controller and makes them available to the view:
+ # helper_attr :name
+ # attr_accessor :name
+ #
+ # ==== Parameters
+ # *attrs<Array[String, Symbol]>:: Names of attributes to be converted
+ # into helpers.
+ def helper_attr(*attrs)
+ attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
+ end
+
+ # Provides a proxy to access helpers methods from outside the view.
+ def helpers
+ @helper_proxy ||= ActionView::Base.new.extend(_helpers)
+ end
+
+ private
+ # Returns a list of modules, normalized from the acceptable kinds of
+ # helpers with the following behavior:
+ # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper",
+ # and "foo_bar_helper.rb" is loaded using require_dependency.
+ # :all:: Loads all modules in the #helpers_dir
+ # Module:: No further processing
+ #
+ # After loading the appropriate files, the corresponding modules
+ # are returned.
+ #
+ # ==== Parameters
+ # args<Array[String, Symbol, Module, all]>:: A list of helpers
+ #
+ # ==== Returns
+ # Array[Module]:: A normalized list of modules for the list of
+ # helpers provided.
+ def _modules_for_helpers(args)
+ args.flatten.map! do |arg|
+ case arg
+ when :all
+ _modules_for_helpers all_application_helpers
+ when String, Symbol
+ file_name = "#{arg.to_s.underscore}_helper"
+ require_dependency(file_name, "Missing helper file helpers/%s.rb")
+ file_name.camelize.constantize
+ when Module
+ arg
+ else
+ raise ArgumentError, "helper must be a String, Symbol, or Module"
+ end
+ end
+ end
+
+ def default_helper_module!
+ module_name = name.sub(/Controller$/, '')
+ module_path = module_name.underscore
+ helper module_path
+ rescue MissingSourceFile => e
+ raise e unless e.is_missing? "#{module_path}_helper"
+ rescue NameError => e
+ raise e unless e.missing_name? "#{module_name}Helper"
+ end
+
+ # Extract helper names from files in app/helpers/**/*.rb
+ def all_application_helpers
+ extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/
+ Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb
new file mode 100644
index 0000000000..af68c772b1
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/hide_actions.rb
@@ -0,0 +1,35 @@
+module ActionController
+ # ActionController::HideActions adds the ability to prevent public methods on a controller
+ # to be called as actions.
+ module HideActions
+ extend ActiveSupport::Concern
+
+ included do
+ extlib_inheritable_accessor(:hidden_actions) { Set.new }
+ end
+
+ private
+
+ # Overrides AbstractController::Base#action_method? to return false if the
+ # action name is in the list of hidden actions.
+ def action_method?(action_name)
+ !hidden_actions.include?(action_name) && super
+ end
+
+ module ClassMethods
+ # Sets all of the actions passed in as hidden actions.
+ #
+ # ==== Parameters
+ # *args<#to_s>:: A list of actions
+ def hide_action(*args)
+ hidden_actions.merge(args.map! {|a| a.to_s })
+ end
+
+ # Overrides AbstractController::Base#action_methods to remove any methods
+ # that are listed as hidden methods.
+ def action_methods
+ @action_methods ||= Set.new(super.reject {|name| hidden_actions.include?(name)})
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
new file mode 100644
index 0000000000..525787bf92
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -0,0 +1,309 @@
+require 'active_support/base64'
+
+module ActionController
+ module HttpAuthentication
+ # Makes it dead easy to do HTTP Basic authentication.
+ #
+ # Simple Basic example:
+ #
+ # class PostsController < ApplicationController
+ # USER_NAME, PASSWORD = "dhh", "secret"
+ #
+ # 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_basic do |user_name, password|
+ # user_name == USER_NAME && password == PASSWORD
+ # end
+ # end
+ # end
+ #
+ #
+ # Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
+ # the regular HTML interface is protected by a session approach:
+ #
+ # class ApplicationController < ActionController::Base
+ # before_filter :set_account, :authenticate
+ #
+ # protected
+ # def set_account
+ # @account = Account.find_by_url_name(request.subdomains.first)
+ # end
+ #
+ # def authenticate
+ # case request.format
+ # when Mime::XML, Mime::ATOM
+ # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
+ # @current_user = user
+ # else
+ # request_http_basic_authentication
+ # end
+ # else
+ # if session_authenticated?
+ # @current_user = @account.users.find(session[:authenticated][:user_id])
+ # else
+ # redirect_to(login_url) and return false
+ # end
+ # end
+ # end
+ # end
+ #
+ # In your integration tests, you can do something like this:
+ #
+ # def test_access_granted_from_xml
+ # get(
+ # "/notes/1.xml", nil,
+ # :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
+ # )
+ #
+ # assert_equal 200, status
+ # 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
+ # FCGI instances. If your environment matches this description and you cannot
+ # authenticate, try this rule in your Apache setup:
+ #
+ # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
+ module Basic
+ extend self
+
+ module ControllerMethods
+ def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
+ authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
+ end
+
+ def authenticate_with_http_basic(&login_procedure)
+ HttpAuthentication::Basic.authenticate(self, &login_procedure)
+ end
+
+ def request_http_basic_authentication(realm = "Application")
+ HttpAuthentication::Basic.authentication_request(self, realm)
+ end
+ end
+
+ def authenticate(controller, &login_procedure)
+ unless authorization(controller.request).blank?
+ login_procedure.call(*user_name_and_password(controller.request))
+ end
+ end
+
+ def user_name_and_password(request)
+ decode_credentials(request).split(/:/, 2)
+ 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
+
+ def decode_credentials(request)
+ ActiveSupport::Base64.decode64(authorization(request).split.last || '')
+ end
+
+ def encode_credentials(user_name, password)
+ "Basic #{ActiveSupport::Base64.encode64("#{user_name}:#{password}")}"
+ end
+
+ def authentication_request(controller, realm)
+ controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
+ controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized
+ 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']
+
+ [true, false].any? do |password_is_ha1|
+ expected = expected_response(method, request.env['REQUEST_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({}) do |hash, pair|
+ key, value = pair.split('=', 2)
+ hash[key.strip.to_sym] = 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 modified by client.
+ def nonce(time = Time.now)
+ t = time.to_i
+ hashed = [t, secret_key]
+ digest = ::Digest::MD5.hexdigest(hashed.join(":"))
+ ActiveSupport::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)
+ t = ActiveSupport::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
diff --git a/actionpack/lib/action_controller/metal/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb
new file mode 100644
index 0000000000..365351b421
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/layouts.rb
@@ -0,0 +1,192 @@
+module ActionController
+ # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
+ # repeated setups. The inclusion pattern has pages that look like this:
+ #
+ # <%= render "shared/header" %>
+ # Hello World
+ # <%= render "shared/footer" %>
+ #
+ # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
+ # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
+ #
+ # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
+ # that the header and footer are only mentioned in one place, like this:
+ #
+ # // The header part of this layout
+ # <%= yield %>
+ # // The footer part of this layout
+ #
+ # And then you have content pages that look like this:
+ #
+ # hello world
+ #
+ # At rendering time, the content page is computed and then inserted in the layout, like this:
+ #
+ # // The header part of this layout
+ # hello world
+ # // The footer part of this layout
+ #
+ # == Accessing shared variables
+ #
+ # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
+ # references that won't materialize before rendering time:
+ #
+ # <h1><%= @page_title %></h1>
+ # <%= yield %>
+ #
+ # ...and content pages that fulfill these references _at_ rendering time:
+ #
+ # <% @page_title = "Welcome" %>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # The result after rendering is:
+ #
+ # <h1>Welcome</h1>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # == Layout assignment
+ #
+ # You can either specify a layout declaratively (using the #layout class method) or give
+ # it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
+ # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
+ #
+ # For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
+ # that template will be used for all actions in PostsController and controllers inheriting
+ # from PostsController.
+ #
+ # If you use a module, for instance Weblog::PostsController, you will need a template named
+ # <tt>app/views/layouts/weblog/posts.html.erb</tt>.
+ #
+ # Since all your controllers inherit from ApplicationController, they will use
+ # <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
+ # or provided.
+ #
+ # == Inheritance Examples
+ #
+ # class BankController < ActionController::Base
+ # layout "bank_standard"
+ #
+ # class InformationController < BankController
+ #
+ # class TellerController < BankController
+ # # teller.html.erb exists
+ #
+ # class TillController < TellerController
+ #
+ # class VaultController < BankController
+ # layout :access_level_layout
+ #
+ # class EmployeeController < BankController
+ # layout nil
+ #
+ # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
+ # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
+ #
+ # The TellerController uses +teller.html.erb+, and TillController inherits that layout and
+ # uses it as well.
+ #
+ # == Types of layouts
+ #
+ # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
+ # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
+ # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
+ #
+ # The method reference is the preferred approach to variable layouts and is used like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout :writers_and_readers
+ #
+ # def index
+ # # fetching posts
+ # end
+ #
+ # private
+ # def writers_and_readers
+ # logged_in? ? "writer_layout" : "reader_layout"
+ # end
+ #
+ # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
+ # is logged in or not.
+ #
+ # If you want to use an inline method, such as a proc, do something like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ #
+ # Of course, the most common way of specifying a layout is still just as a plain template name:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard"
+ #
+ # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
+ # Otherwise, it will be looked up relative to the template root.
+ #
+ # == Conditional layouts
+ #
+ # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
+ # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
+ # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard", :except => :rss
+ #
+ # # ...
+ #
+ # end
+ #
+ # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
+ # around the rendered view.
+ #
+ # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
+ # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
+ #
+ # == Using a different layout in the action render call
+ #
+ # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
+ # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
+ # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard"
+ #
+ # def help
+ # render :action => "help", :layout => "help"
+ # end
+ # end
+ #
+ # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout.
+ module Layouts
+ extend ActiveSupport::Concern
+
+ include ActionController::Renderer
+ include AbstractController::Layouts
+
+ module ClassMethods
+ # If no layout is provided, look for a layout with this name.
+ def _implied_layout_name
+ controller_path
+ end
+ end
+
+ private
+ def _determine_template(options)
+ super
+
+ return if (options.key?(:text) || options.key?(:inline) || options.key?(:partial)) && !options.key?(:layout)
+ layout = options.key?(:layout) ? options[:layout] : :default
+ options[:_layout] = _layout_for_option(layout, options[:_template].details)
+ end
+
+ def _layout_for_option(name, details)
+ case name
+ when String then _layout_for_name(name, details)
+ when true then _default_layout(details, true)
+ when :default then _default_layout(details, false)
+ when false, nil then nil
+ else
+ raise ArgumentError,
+ "String, true, or false, expected for `layout'; you passed #{name.inspect}"
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
new file mode 100644
index 0000000000..f4a4007a43
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -0,0 +1,353 @@
+module ActionController #:nodoc:
+ module MimeResponds #:nodoc:
+ extend ActiveSupport::Concern
+
+ included do
+ class_inheritable_reader :mimes_for_respond_to
+ clear_respond_to
+ end
+
+ module ClassMethods
+ # Defines mimes that are rendered by default when invoking respond_with.
+ #
+ # Examples:
+ #
+ # respond_to :html, :xml, :json
+ #
+ # All actions on your controller will respond to :html, :xml and :json.
+ #
+ # But if you want to specify it based on your actions, you can use only and
+ # except:
+ #
+ # respond_to :html
+ # respond_to :xml, :json, :except => [ :edit ]
+ #
+ # The definition above explicits that all actions respond to :html. And all
+ # actions except :edit respond to :xml and :json.
+ #
+ # You can specify also only parameters:
+ #
+ # respond_to :rjs, :only => :create
+ #
+ def respond_to(*mimes)
+ options = mimes.extract_options!
+
+ only_actions = Array(options.delete(:only))
+ except_actions = Array(options.delete(:except))
+
+ mimes.each do |mime|
+ mime = mime.to_sym
+ mimes_for_respond_to[mime] = {}
+ mimes_for_respond_to[mime][:only] = only_actions unless only_actions.empty?
+ mimes_for_respond_to[mime][:except] = except_actions unless except_actions.empty?
+ end
+ end
+
+ # Clear all mimes in respond_to.
+ #
+ def clear_respond_to
+ write_inheritable_attribute(:mimes_for_respond_to, ActiveSupport::OrderedHash.new)
+ end
+ end
+
+ # Without web-service support, an action which collects the data for displaying a list of people
+ # might look something like this:
+ #
+ # def index
+ # @people = Person.find(:all)
+ # end
+ #
+ # Here's the same action, with web-service support baked in:
+ #
+ # def index
+ # @people = Person.find(:all)
+ #
+ # respond_to do |format|
+ # format.html
+ # format.xml { render :xml => @people.to_xml }
+ # end
+ # end
+ #
+ # What that says is, "if the client wants HTML in response to this action, just respond as we
+ # would have before, but if the client wants XML, return them the list of people in XML format."
+ # (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
+ #
+ # Supposing you have an action that adds a new person, optionally creating their company
+ # (by name) if it does not already exist, without web-services, it might look like this:
+ #
+ # def create
+ # @company = Company.find_or_create_by_name(params[:company][:name])
+ # @person = @company.people.create(params[:person])
+ #
+ # redirect_to(person_list_url)
+ # end
+ #
+ # Here's the same action, with web-service support baked in:
+ #
+ # def create
+ # company = params[:person].delete(:company)
+ # @company = Company.find_or_create_by_name(company[:name])
+ # @person = @company.people.create(params[:person])
+ #
+ # respond_to do |format|
+ # format.html { redirect_to(person_list_url) }
+ # format.js
+ # format.xml { render :xml => @person.to_xml(:include => @company) }
+ # end
+ # end
+ #
+ # If the client wants HTML, we just redirect them back to the person list. If they want Javascript
+ # (format.js), then it is an RJS request and we render the RJS template associated with this action.
+ # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
+ # include the person's company in the rendered XML, so you get something like this:
+ #
+ # <person>
+ # <id>...</id>
+ # ...
+ # <company>
+ # <id>...</id>
+ # <name>...</name>
+ # ...
+ # </company>
+ # </person>
+ #
+ # Note, however, the extra bit at the top of that action:
+ #
+ # company = params[:person].delete(:company)
+ # @company = Company.find_or_create_by_name(company[:name])
+ #
+ # This is because the incoming XML document (if a web-service request is in process) can only contain a
+ # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
+ #
+ # person[name]=...&person[company][name]=...&...
+ #
+ # And, like this (xml-encoded):
+ #
+ # <person>
+ # <name>...</name>
+ # <company>
+ # <name>...</name>
+ # </company>
+ # </person>
+ #
+ # In other words, we make the request so that it operates on a single entity's person. Then, in the action,
+ # we extract the company data from the request, find or create the company, and then create the new person
+ # with the remaining data.
+ #
+ # Note that you can define your own XML parameter parser which would allow you to describe multiple entities
+ # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow
+ # and accept Rails' defaults, life will be much easier.
+ #
+ # If you need to use a MIME type which isn't supported by default, you can register your own handlers in
+ # environment.rb as follows.
+ #
+ # Mime::Type.register "image/jpg", :jpg
+ #
+ # Respond to also allows you to specify a common block for different formats by using any:
+ #
+ # def index
+ # @people = Person.find(:all)
+ #
+ # respond_to do |format|
+ # format.html
+ # format.any(:xml, :json) { render request.format.to_sym => @people }
+ # end
+ # end
+ #
+ # In the example above, if the format is xml, it will render:
+ #
+ # render :xml => @people
+ #
+ # Or if the format is json:
+ #
+ # render :json => @people
+ #
+ # Since this is a common pattern, you can use the class method respond_to
+ # with the respond_with method to have the same results:
+ #
+ # class PeopleController < ApplicationController
+ # respond_to :html, :xml, :json
+ #
+ # def index
+ # @people = Person.find(:all)
+ # respond_with(@person)
+ # end
+ # end
+ #
+ # Be sure to check respond_with and respond_to documentation for more examples.
+ #
+ def respond_to(*mimes, &block)
+ options = mimes.extract_options!
+ raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
+
+ resource = options.delete(:with)
+ responder = Responder.new
+
+ mimes = collect_mimes_from_class_level if mimes.empty?
+ mimes.each { |mime| responder.send(mime) }
+ block.call(responder) if block_given?
+
+ if format = request.negotiate_mime(responder.order)
+ respond_to_block_or_template_or_resource(format, resource,
+ options, &responder.response_for(format))
+ else
+ head :not_acceptable
+ end
+ end
+
+ # respond_with allows you to respond an action with a given resource. It
+ # requires that you set your class with a :respond_to method with the
+ # formats allowed:
+ #
+ # class PeopleController < ApplicationController
+ # respond_to :html, :xml, :json
+ #
+ # def index
+ # @people = Person.find(:all)
+ # respond_with(@person)
+ # end
+ # end
+ #
+ # When a request comes with format :xml, the respond_with will first search
+ # for a template as person/index.xml, if the template is not available, it
+ # will see if the given resource responds to :to_xml.
+ #
+ # If neither are available, it will raise an error.
+ #
+ # Extra parameters given to respond_with are used when :to_format is invoked.
+ # This allows you to set status and location for several formats at the same
+ # time. Consider this restful controller response on create for both xml
+ # and json formats:
+ #
+ # class PeopleController < ApplicationController
+ # respond_to :xml, :json
+ #
+ # def create
+ # @person = Person.new(params[:person])
+ #
+ # if @person.save
+ # respond_with(@person, :status => :ok, :location => person_url(@person))
+ # else
+ # respond_with(@person.errors, :status => :unprocessable_entity)
+ # end
+ # end
+ # end
+ #
+ # Finally, respond_with also accepts blocks, as in respond_to. Let's take
+ # the same controller and create action above and add common html behavior:
+ #
+ # class PeopleController < ApplicationController
+ # respond_to :html, :xml, :json
+ #
+ # def create
+ # @person = Person.new(params[:person])
+ #
+ # if @person.save
+ # options = { :status => :ok, :location => person_url(@person) }
+ #
+ # respond_with(@person, options) do |format|
+ # format.html { redirect_to options[:location] }
+ # end
+ # else
+ # respond_with(@person.errors, :status => :unprocessable_entity) do
+ # format.html { render :action => :new }
+ # end
+ # end
+ # end
+ # end
+ #
+ def respond_with(resource, options={}, &block)
+ respond_to(options.merge!(:with => resource), &block)
+ end
+
+ protected
+
+ def respond_to_block_or_template_or_resource(format, resource, options)
+ self.formats = [format.to_sym]
+ return yield if block_given?
+
+ begin
+ default_render
+ rescue ActionView::MissingTemplate => e
+ if resource && resource.respond_to?(:"to_#{format.to_sym}")
+ render options.merge(format.to_sym => resource)
+ else
+ raise e
+ end
+ end
+ end
+
+ # Collect mimes declared in the class method respond_to valid for the
+ # current action.
+ #
+ def collect_mimes_from_class_level #:nodoc:
+ action = action_name.to_sym
+
+ mimes_for_respond_to.keys.select do |mime|
+ config = mimes_for_respond_to[mime]
+
+ if config[:except]
+ !config[:except].include?(action)
+ elsif config[:only]
+ config[:only].include?(action)
+ else
+ true
+ end
+ end
+ end
+
+ class Responder #:nodoc:
+ attr_accessor :order
+
+ def initialize
+ @order, @responses = [], {}
+ end
+
+ def any(*args, &block)
+ if args.any?
+ args.each { |type| send(type, &block) }
+ else
+ custom(Mime::ALL, &block)
+ end
+ end
+ alias :all :any
+
+ def custom(mime_type, &block)
+ mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
+
+ @order << mime_type
+ @responses[mime_type] ||= block
+ end
+
+ def response_for(mime)
+ @responses[mime] || @responses[Mime::ALL]
+ end
+
+ def self.generate_method_for_mime(mime)
+ 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
+
+ Mime::SET.each do |mime|
+ 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
+ super
+ end
+ end
+
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/rack_convenience.rb b/actionpack/lib/action_controller/metal/rack_convenience.rb
new file mode 100644
index 0000000000..805157b0e3
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/rack_convenience.rb
@@ -0,0 +1,33 @@
+module ActionController
+ module RackConvenience
+ extend ActiveSupport::Concern
+
+ included do
+ delegate :headers, :status=, :location=, :content_type=,
+ :status, :location, :content_type, :to => "@_response"
+ attr_internal :request, :response
+ end
+
+ def call(name, env)
+ @_request = ActionDispatch::Request.new(env)
+ @_response = ActionDispatch::Response.new
+ @_response.request = request
+ super
+ end
+
+ def params
+ @_params ||= @_request.parameters
+ end
+
+ # :api: private
+ def to_rack
+ @_response.prepare!
+ @_response.to_a
+ end
+
+ def response_body=(body)
+ response.body = body if response
+ super
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/redirector.rb b/actionpack/lib/action_controller/metal/redirector.rb
new file mode 100644
index 0000000000..20060b001f
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/redirector.rb
@@ -0,0 +1,19 @@
+module ActionController
+ class RedirectBackError < AbstractController::Error #:nodoc:
+ DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].'
+
+ def initialize(message = nil)
+ super(message || DEFAULT_MESSAGE)
+ end
+ end
+
+ module Redirector
+ def redirect_to(url, status) #:doc:
+ raise AbstractController::DoubleRenderError if response_body
+ logger.info("Redirected to #{url}") if logger && logger.info?
+ self.status = status
+ self.location = url.gsub(/[\r\n]/, '')
+ self.response_body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/render_options.rb b/actionpack/lib/action_controller/metal/render_options.rb
new file mode 100644
index 0000000000..65ee09883e
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/render_options.rb
@@ -0,0 +1,103 @@
+module ActionController
+ module RenderOptions
+ extend ActiveSupport::Concern
+
+ included do
+ extlib_inheritable_accessor :_renderers
+ self._renderers = []
+ end
+
+ module ClassMethods
+ def _write_render_options
+ renderers = _renderers.map do |r|
+ <<-RUBY_EVAL
+ if options.key?(:#{r})
+ _process_options(options)
+ return _render_#{r}(options[:#{r}], options)
+ end
+ RUBY_EVAL
+ end
+
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def _handle_render_options(options)
+ #{renderers.join}
+ end
+ RUBY_EVAL
+ end
+
+ def _add_render_option(name)
+ _renderers << name
+ _write_render_options
+ end
+ end
+
+ def render_to_body(options)
+ _handle_render_options(options) || super
+ end
+ end
+
+ module RenderOption #:nodoc:
+ def self.extended(base)
+ base.extend ActiveSupport::Concern
+ base.send :include, ::ActionController::RenderOptions
+
+ def base.register_renderer(name)
+ included { _add_render_option(name) }
+ end
+ end
+ end
+
+ module Renderers
+ module Json
+ extend RenderOption
+ register_renderer :json
+
+ def _render_json(json, options)
+ json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str)
+ json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
+ self.content_type ||= Mime::JSON
+ self.response_body = json
+ end
+ end
+
+ module Js
+ extend RenderOption
+ register_renderer :js
+
+ def _render_js(js, options)
+ self.content_type ||= Mime::JS
+ self.response_body = js
+ end
+ end
+
+ module Xml
+ extend RenderOption
+ register_renderer :xml
+
+ def _render_xml(xml, options)
+ self.content_type ||= Mime::XML
+ self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml
+ end
+ end
+
+ module RJS
+ extend RenderOption
+ register_renderer :update
+
+ def _render_update(proc, options)
+ generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc)
+ self.content_type = Mime::JS
+ self.response_body = generator.to_s
+ end
+ end
+
+ module All
+ extend ActiveSupport::Concern
+
+ include ActionController::Renderers::Json
+ include ActionController::Renderers::Js
+ include ActionController::Renderers::Xml
+ include ActionController::Renderers::RJS
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/renderer.rb b/actionpack/lib/action_controller/metal/renderer.rb
new file mode 100644
index 0000000000..572da451ff
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/renderer.rb
@@ -0,0 +1,77 @@
+module ActionController
+ module Renderer
+ extend ActiveSupport::Concern
+
+ include AbstractController::Renderer
+
+ def process_action(*)
+ self.formats = request.formats.map {|x| x.to_sym}
+ super
+ end
+
+ def render(options)
+ super
+ self.content_type ||= begin
+ mime = options[:_template].mime_type
+ formats.include?(mime && mime.to_sym) || formats.include?(:all) ? mime : Mime::Type.lookup_by_extension(formats.first)
+ end.to_s
+ response_body
+ end
+
+ def render_to_body(options)
+ _process_options(options)
+
+ if options.key?(:partial)
+ _render_partial(options[:partial], options)
+ end
+
+ super
+ end
+
+ private
+ def _prefix
+ controller_path
+ end
+
+ def _determine_template(options)
+ if options.key?(:text)
+ options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first)
+ elsif options.key?(:inline)
+ handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb")
+ template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
+ options[:_template] = template
+ elsif options.key?(:template)
+ options[:_template_name] = options[:template]
+ elsif options.key?(:file)
+ options[:_template_name] = options[:file]
+ elsif !options.key?(:partial)
+ options[:_template_name] = (options[:action] || action_name).to_s
+ options[:_prefix] = _prefix
+ end
+
+ super
+ end
+
+ def _render_partial(partial, options)
+ case partial
+ when true
+ options[:_prefix] = _prefix
+ when String
+ options[:_prefix] = _prefix unless partial.index('/')
+ options[:_template_name] = partial
+ else
+ options[:_partial_object] = true
+ return
+ end
+
+ options[:_partial] = options[:object] || true
+ end
+
+ def _process_options(options)
+ status, content_type, location = options.values_at(:status, :content_type, :location)
+ self.status = status if status
+ self.content_type = content_type if content_type
+ self.headers["Location"] = url_for(location) if location
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
new file mode 100644
index 0000000000..ad06657f86
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -0,0 +1,118 @@
+module ActionController #:nodoc:
+ class InvalidAuthenticityToken < ActionControllerError #:nodoc:
+ end
+
+ module RequestForgeryProtection
+ extend ActiveSupport::Concern
+
+ # TODO : Remove the defined? check when new base is the main base
+ include AbstractController::Helpers, Session
+
+ included do
+ # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
+ # sets it to <tt>:authenticity_token</tt> by default.
+ cattr_accessor :request_forgery_protection_token
+
+ # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
+ class_inheritable_accessor :allow_forgery_protection
+ self.allow_forgery_protection = true
+
+ helper_method :form_authenticity_token
+ helper_method :protect_against_forgery?
+ end
+
+ # 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 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
+ # 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.
+ #
+ # This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an
+ # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in
+ # production by editing public/422.html. A call to this method in ApplicationController is generated by default in post-Rails 2.0
+ # applications.
+ #
+ # The token parameter is named <tt>authenticity_token</tt> by default. If you are generating an HTML form manually (without the
+ # use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to include a hidden field named like that and
+ # set its value to what is returned by <tt>form_authenticity_token</tt>. Same applies to manually constructed Ajax requests. To
+ # make the token available through a global variable to scripts on a certain page, you could add something like this to a view:
+ #
+ # <%= javascript_tag "window._token = '#{form_authenticity_token}'" %>
+ #
+ # Request forgery protection is disabled by default in test environment. If you are upgrading from Rails 1.x, add this to
+ # config/environments/test.rb:
+ #
+ # # Disable request forgery protection in test environment
+ # config.action_controller.allow_forgery_protection = false
+ #
+ # == Learn more about CSRF (Cross-Site Request Forgery) attacks
+ #
+ # Here are some resources:
+ # * http://isc.sans.org/diary.html?storyid=1750
+ # * http://en.wikipedia.org/wiki/Cross-site_request_forgery
+ #
+ # Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application.
+ # There are a few guidelines you should follow:
+ #
+ # * Keep your GET requests safe and idempotent. More reading material:
+ # * http://www.xml.com/pub/a/2002/04/24/deviant.html
+ # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
+ # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session"
+ #
+ module ClassMethods
+ # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
+ #
+ # Example:
+ #
+ # class FooController < ApplicationController
+ # protect_from_forgery :except => :index
+ #
+ # # you can disable csrf protection on controller-by-controller basis:
+ # skip_before_filter :verify_authenticity_token
+ # end
+ #
+ # Valid Options:
+ #
+ # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
+ def protect_from_forgery(options = {})
+ self.request_forgery_protection_token ||= :authenticity_token
+ before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except)
+ 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
+
+ protected
+ # The actual before_filter that is used. Modify this to change how you handle unverified requests.
+ def verify_authenticity_token
+ verified_request? || raise(ActionController::InvalidAuthenticityToken)
+ end
+
+ # Returns true or false if a request is verified. Checks:
+ #
+ # * is the format restricted? By default, only HTML requests are checked.
+ # * is it a GET request? Gets should be safe and idempotent
+ # * Does the form_authenticity_token match the given token value from the params?
+ def verified_request?
+ !protect_against_forgery? ||
+ request.method == :get ||
+ request.xhr? ||
+ !verifiable_request_format? ||
+ form_authenticity_token == params[request_forgery_protection_token]
+ end
+
+ def verifiable_request_format?
+ !request.content_type.nil? && request.content_type.verify_request?
+ end
+
+ # Sets the token value for the current session.
+ def form_authenticity_token
+ session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
+ end
+
+ def protect_against_forgery?
+ allow_forgery_protection && request_forgery_protection_token
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/rescuable.rb b/actionpack/lib/action_controller/metal/rescuable.rb
new file mode 100644
index 0000000000..029e643d93
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/rescuable.rb
@@ -0,0 +1,52 @@
+module ActionController #:nodoc:
+ # Actions that fail to perform as expected throw exceptions. These
+ # exceptions can either be rescued for the public view (with a nice
+ # 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 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. Custom rescue
+ # behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
+ # and <tt>rescue_action_locally</tt> methods.
+ module Rescue
+ extend ActiveSupport::Concern
+
+ included do
+ include ActiveSupport::Rescuable
+ end
+
+ module ClassMethods
+ # This can be removed once we can move action(:_rescue_action) into middlewares.rb
+ # Currently, it does controller.method(:rescue_action), which is hiding the implementation
+ # difference between the old and new base.
+ def rescue_action(env)
+ action(:_rescue_action).call(env)
+ end
+ end
+
+ attr_internal :rescued_exception
+
+ private
+ def method_for_action(action_name)
+ return action_name if self.rescued_exception = request.env.delete("action_dispatch.rescue.exception")
+ super
+ end
+
+ def _rescue_action
+ rescue_with_handler(rescued_exception) || raise(rescued_exception)
+ end
+
+ def process_action(*)
+ super
+ rescue Exception => exception
+ self.rescued_exception = exception
+ _rescue_action
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/session.rb b/actionpack/lib/action_controller/metal/session.rb
new file mode 100644
index 0000000000..bcedd6e1c7
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/session.rb
@@ -0,0 +1,15 @@
+module ActionController
+ module Session
+ extend ActiveSupport::Concern
+
+ include RackConvenience
+
+ def session
+ @_request.session
+ end
+
+ def reset_session
+ @_request.reset_session
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb
new file mode 100644
index 0000000000..ffce8e1bd1
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/session_management.rb
@@ -0,0 +1,54 @@
+module ActionController #:nodoc:
+ module SessionManagement #:nodoc:
+ def self.included(base)
+ base.class_eval do
+ extend ClassMethods
+ end
+ end
+
+ module ClassMethods
+ # 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>),
+ # but you can also specify one of the other included stores (<tt>:active_record_store</tt>,
+ # <tt>:mem_cache_store</tt>, or your own custom class.
+ def session_store=(store)
+ if store == :active_record_store
+ self.session_store = ActiveRecord::SessionStore
+ else
+ @@session_store = store.is_a?(Symbol) ?
+ Session.const_get(store.to_s.camelize) :
+ store
+ end
+ end
+
+ # Returns the session store class currently used.
+ def session_store
+ if defined? @@session_store
+ @@session_store
+ else
+ ActionDispatch::Session::CookieStore
+ end
+ end
+
+ def session=(options = {})
+ self.session_store = nil if options.delete(:disabled)
+ session_options.merge!(options)
+ end
+
+ # Returns the hash used to configure the session. Example use:
+ #
+ # ActionController::Base.session_options[:secure] = true # session only available over HTTPS
+ def session_options
+ @session_options ||= {}
+ end
+
+ def session(*args)
+ ActiveSupport::Deprecation.warn(
+ "Disabling sessions for a single controller has been deprecated. " +
+ "Sessions are now lazy loaded. So if you don't access them, " +
+ "consider them off. You can still modify the session cookie " +
+ "options with request.session_options.", caller)
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
new file mode 100644
index 0000000000..f0317c6e99
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -0,0 +1,187 @@
+require 'active_support/core_ext/string/bytesize'
+
+module ActionController #:nodoc:
+ # Methods for sending arbitrary data and for streaming files to the browser,
+ # instead of rendering.
+ module Streaming
+ extend ActiveSupport::Concern
+
+ include ActionController::Renderer
+
+ DEFAULT_SEND_FILE_OPTIONS = {
+ :type => 'application/octet-stream'.freeze,
+ :disposition => 'attachment'.freeze,
+ :stream => true,
+ :buffer_size => 4096,
+ :x_sendfile => false
+ }.freeze
+
+ X_SENDFILE_HEADER = 'X-Sendfile'.freeze
+
+ protected
+ # Sends the file, by default streaming it 4096 bytes at a time. This way the
+ # whole file doesn't need to be read into memory at once. This makes it
+ # feasible to send even large files. You can optionally turn off streaming
+ # and send the whole file at once.
+ #
+ # Be careful to sanitize the path parameter if it is coming from a web
+ # page. <tt>send_file(params[:path])</tt> allows a malicious user to
+ # download any file on your server.
+ #
+ # Options:
+ # * <tt>:filename</tt> - suggests a filename for the browser to use.
+ # Defaults to <tt>File.basename(path)</tt>.
+ # * <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
+ # 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.
+ # Valid values are 'inline' and 'attachment' (default).
+ # * <tt>:stream</tt> - whether to send the file to the user agent as it is read (+true+)
+ # or to read the entire file before sending (+false+). Defaults to +true+.
+ # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
+ # Defaults to 4096.
+ # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
+ # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
+ # the URL, which is necessary for i18n filenames on certain browsers
+ # (setting <tt>:filename</tt> overrides this option).
+ # * <tt>:x_sendfile</tt> - uses X-Sendfile to send the file when set to +true+. This is currently
+ # only available with Lighttpd/Apache2 and specific modules installed and activated. Since this
+ # uses the web server to send the file, this may lower memory consumption on your server and
+ # it will not block your application for further requests.
+ # See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and
+ # http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+.
+ #
+ # The default Content-Type and Content-Disposition headers are
+ # set to download arbitrary binary files in as many browsers as
+ # possible. IE versions 4, 5, 5.5, and 6 are all known to have
+ # a variety of quirks (especially when downloading over SSL).
+ #
+ # Simple download:
+ #
+ # send_file '/path/to.zip'
+ #
+ # Show a JPEG in the browser:
+ #
+ # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
+ #
+ # Show a 404 page in the browser:
+ #
+ # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404
+ #
+ # Read about the other Content-* HTTP headers if you'd like to
+ # provide the user with more information (such as Content-Description) in
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11.
+ #
+ # Also be aware that the document may be cached by proxies and browsers.
+ # The Pragma and Cache-Control headers declare how the file may be cached
+ # by intermediaries. They default to require clients to validate with
+ # the server before releasing cached responses. See
+ # http://www.mnot.net/cache_docs/ for an overview of web caching and
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ # for the Cache-Control header spec.
+ def send_file(path, options = {}) #:doc:
+ raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
+
+ options[:length] ||= File.size(path)
+ options[:filename] ||= File.basename(path) unless options[:url_based_filename]
+ send_file_headers! options
+
+ @performed_render = false
+
+ if options[:x_sendfile]
+ logger.info "Sending #{X_SENDFILE_HEADER} header #{path}" if logger
+ head options[:status], X_SENDFILE_HEADER => path
+ else
+ if options[:stream]
+ # TODO : Make render :text => proc {} work with the new base
+ render :status => options[:status], :text => Proc.new { |response, output|
+ logger.info "Streaming file #{path}" unless logger.nil?
+ len = options[:buffer_size] || 4096
+ File.open(path, 'rb') do |file|
+ while buf = file.read(len)
+ output.write(buf)
+ end
+ end
+ }
+ else
+ logger.info "Sending file #{path}" unless logger.nil?
+ File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
+ end
+ end
+ end
+
+ # Sends the given binary data to the browser. This method is similar to
+ # <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:
+ # * <tt>:filename</tt> - suggests a filename for the browser to use.
+ # * <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.
+ # Valid values are 'inline' and 'attachment' (default).
+ # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
+ #
+ # Generic data download:
+ #
+ # send_data buffer
+ #
+ # Download a dynamically-generated tarball:
+ #
+ # send_data generate_tgz('dir'), :filename => 'dir.tgz'
+ #
+ # Display an image Active Record in the browser:
+ #
+ # send_data image.data, :type => image.content_type, :disposition => 'inline'
+ #
+ # 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:
+ logger.info "Sending data #{options[:filename]}" if logger
+ send_file_headers! options.merge(:length => data.bytesize)
+ @performed_render = false
+ render :status => options[:status], :text => data
+ end
+
+ private
+ def send_file_headers!(options)
+ options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
+ [:length, :type, :disposition].each do |arg|
+ raise ArgumentError, ":#{arg} option required" if options[arg].nil?
+ end
+
+ disposition = options[:disposition].dup || 'attachment'
+
+ disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
+
+ content_type = options[:type]
+
+ if content_type.is_a?(Symbol)
+ raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.key?(content_type.to_s)
+ self.content_type = Mime::Type.lookup_by_extension(content_type.to_s)
+ else
+ self.content_type = content_type
+ end
+
+ headers.merge!(
+ 'Content-Length' => options[:length].to_s,
+ 'Content-Disposition' => disposition,
+ 'Content-Transfer-Encoding' => 'binary'
+ )
+
+ # Fix a problem with IE 6.0 on opening downloaded files:
+ # If Cache-Control: no-cache is set (which Rails does by default),
+ # IE removes the file it just downloaded from its cache immediately
+ # after it displays the "open/save" dialog, which means that if you
+ # hit "open" the file isn't there anymore when the application that
+ # is called for handling the download is run, so let's workaround that
+ response.cache_control[:public] ||= false
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
new file mode 100644
index 0000000000..a4a1116d9e
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -0,0 +1,39 @@
+module ActionController
+ module Testing
+ extend ActiveSupport::Concern
+
+ include RackConvenience
+
+ # OMG MEGA HAX
+ def process_with_new_base_test(request, response)
+ @_request = request
+ @_response = response
+ @_response.request = request
+ ret = process(request.parameters[:action])
+ @_response.body ||= self.response_body
+ @_response.prepare!
+ set_test_assigns
+ ret
+ end
+
+ def set_test_assigns
+ @assigns = {}
+ (instance_variable_names - self.class.protected_instance_variables).each do |var|
+ name, value = var[1..-1], instance_variable_get(var)
+ @assigns[name] = value
+ end
+ end
+
+ # TODO : Rewrite tests using controller.headers= to use Rack env
+ def headers=(new_headers)
+ @_response ||= ActionDispatch::Response.new
+ @_response.headers.replace(new_headers)
+ end
+
+ module ClassMethods
+ def before_filters
+ _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
new file mode 100644
index 0000000000..7119c14cd3
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -0,0 +1,49 @@
+module ActionController
+ module UrlFor
+ extend ActiveSupport::Concern
+
+ include RackConvenience
+
+ def process_action(*)
+ initialize_current_url
+ super
+ end
+
+ def initialize_current_url
+ @url = UrlRewriter.new(request, params.clone)
+ end
+
+ # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
+ # the form of a hash, just like the one you would use for url_for directly. Example:
+ #
+ # def default_url_options(options)
+ # { :project => @project.active? ? @project.url_name : "unknown" }
+ # end
+ #
+ # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
+ # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
+ # by this method.
+ def default_url_options(options = nil)
+ end
+
+ def rewrite_options(options) #:nodoc:
+ if defaults = default_url_options(options)
+ defaults.merge(options)
+ else
+ options
+ end
+ end
+
+ def url_for(options = {})
+ options ||= {}
+ case options
+ when String
+ options
+ when Hash
+ @url.rewrite(rewrite_options(options))
+ else
+ polymorphic_url(options)
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/verification.rb b/actionpack/lib/action_controller/metal/verification.rb
new file mode 100644
index 0000000000..951ae1bee1
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/verification.rb
@@ -0,0 +1,130 @@
+module ActionController #:nodoc:
+ module Verification #:nodoc:
+ extend ActiveSupport::Concern
+
+ include AbstractController::Callbacks, Session, Flash, Renderer
+
+ # This module provides a class-level method for specifying that certain
+ # actions are guarded against being called without certain prerequisites
+ # being met. This is essentially a special kind of before_filter.
+ #
+ # An action may be guarded against being invoked without certain request
+ # parameters being set, or without certain session values existing.
+ #
+ # When a verification is violated, values may be inserted into the flash, and
+ # a specified redirection is triggered. If no specific action is configured,
+ # verification failures will by default result in a 400 Bad Request response.
+ #
+ # Usage:
+ #
+ # class GlobalController < ActionController::Base
+ # # Prevent the #update_settings action from being invoked unless
+ # # the 'admin_privileges' request parameter exists. The
+ # # settings action will be redirected to in current controller
+ # # if verification fails.
+ # verify :params => "admin_privileges", :only => :update_post,
+ # :redirect_to => { :action => "settings" }
+ #
+ # # Disallow a post from being updated if there was no information
+ # # submitted with the post, and if there is no active post in the
+ # # session, and if there is no "note" key in the flash. The route
+ # # named category_url will be redirected to if verification fails.
+ #
+ # verify :params => "post", :session => "post", "flash" => "note",
+ # :only => :update_post,
+ # :add_flash => { "alert" => "Failed to create your message" },
+ # :redirect_to => :category_url
+ #
+ # Note that these prerequisites are not business rules. They do not examine
+ # the content of the session or the parameters. That level of validation should
+ # be encapsulated by your domain model or helper methods in the controller.
+ module ClassMethods
+ # Verify the given actions so that if certain prerequisites are not met,
+ # the user is redirected to a different action. The +options+ parameter
+ # is a hash consisting of the following key/value pairs:
+ #
+ # <tt>:params</tt>::
+ # a single key or an array of keys that must be in the <tt>params</tt>
+ # hash in order for the action(s) to be safely called.
+ # <tt>:session</tt>::
+ # a single key or an array of keys that must be in the <tt>session</tt>
+ # in order for the action(s) to be safely called.
+ # <tt>:flash</tt>::
+ # a single key or an array of keys that must be in the flash in order
+ # for the action(s) to be safely called.
+ # <tt>:method</tt>::
+ # a single key or an array of keys--any one of which must match the
+ # current request method in order for the action(s) to be safely called.
+ # (The key should be a symbol: <tt>:get</tt> or <tt>:post</tt>, for
+ # example.)
+ # <tt>:xhr</tt>::
+ # true/false option to ensure that the request is coming from an Ajax
+ # call or not.
+ # <tt>:add_flash</tt>::
+ # a hash of name/value pairs that should be merged into the session's
+ # flash if the prerequisites cannot be satisfied.
+ # <tt>:add_headers</tt>::
+ # a hash of name/value pairs that should be merged into the response's
+ # headers hash if the prerequisites cannot be satisfied.
+ # <tt>:redirect_to</tt>::
+ # the redirection parameters to be used when redirecting if the
+ # prerequisites cannot be satisfied. You can redirect either to named
+ # route or to the action in some controller.
+ # <tt>:render</tt>::
+ # the render parameters to be used when the prerequisites cannot be satisfied.
+ # <tt>:only</tt>::
+ # only apply this verification to the actions specified in the associated
+ # array (may also be a single value).
+ # <tt>:except</tt>::
+ # do not apply this verification to the actions specified in the associated
+ # array (may also be a single value).
+ def verify(options={})
+ before_filter :only => options[:only], :except => options[:except] do |c|
+ c.__send__ :verify_action, options
+ end
+ end
+ end
+
+ private
+
+ def verify_action(options) #:nodoc:
+ if prereqs_invalid?(options)
+ flash.update(options[:add_flash]) if options[:add_flash]
+ response.headers.merge!(options[:add_headers]) if options[:add_headers]
+ apply_remaining_actions(options) unless performed?
+ end
+ end
+
+ def prereqs_invalid?(options) # :nodoc:
+ verify_presence_of_keys_in_hash_flash_or_params(options) ||
+ verify_method(options) ||
+ verify_request_xhr_status(options)
+ end
+
+ def verify_presence_of_keys_in_hash_flash_or_params(options) # :nodoc:
+ [*options[:params] ].find { |v| v && params[v.to_sym].nil? } ||
+ [*options[:session]].find { |v| session[v].nil? } ||
+ [*options[:flash] ].find { |v| flash[v].nil? }
+ end
+
+ def verify_method(options) # :nodoc:
+ [*options[:method]].all? { |v| request.method != v.to_sym } if options[:method]
+ end
+
+ def verify_request_xhr_status(options) # :nodoc:
+ request.xhr? != options[:xhr] unless options[:xhr].nil?
+ end
+
+ def apply_redirect_to(redirect_to_option) # :nodoc:
+ (redirect_to_option.is_a?(Symbol) && redirect_to_option != :back) ? self.__send__(redirect_to_option) : redirect_to_option
+ end
+
+ def apply_remaining_actions(options) # :nodoc:
+ case
+ when options[:render] ; render(options[:render])
+ when options[:redirect_to] ; redirect_to(apply_redirect_to(options[:redirect_to]))
+ else head(:bad_request)
+ end
+ end
+ end
+end \ No newline at end of file