diff options
Diffstat (limited to 'actionpack/lib/action_controller')
26 files changed, 429 insertions, 199 deletions
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 3b82231b15..71425cd542 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -171,6 +171,16 @@ module ActionController class Base < Metal abstract! + # Shortcut helper that returns all the ActionController::Base modules except the ones passed in the argument: + # + # class MetalController + # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left| + # include left + # end + # end + # + # This gives better control over what you want to exclude and makes it easier + # to create a bare controller class, instead of listing the modules required manually. def self.without_modules(*modules) modules = modules.map do |m| m.is_a?(Symbol) ? ActionController.const_get(m) : m @@ -192,7 +202,6 @@ module ActionController Renderers::All, ConditionalGet, RackDelegation, - SessionManagement, Caching, MimeResponds, ImplicitRender, diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index ba96735e56..80901b8bf3 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -40,7 +40,7 @@ module ActionController #:nodoc: # # You can modify the default action cache path by passing a # <tt>:cache_path</tt> option. This will be passed directly to - # <tt>ActionCachePath.path_for</tt>. This is handy for actions with + # <tt>ActionCachePath.new</tt>. This is handy for actions with # multiple possible routes that should be cached differently. If a # block is given, it is called with the current controller instance. # @@ -133,6 +133,8 @@ module ActionController #:nodoc: end def filter(controller) + cache_layout = @cache_layout.respond_to?(:call) ? @cache_layout.call(controller) : @cache_layout + path_options = if @cache_path.respond_to?(:call) controller.instance_exec(controller, &@cache_path) else @@ -144,13 +146,13 @@ module ActionController #:nodoc: body = controller.read_fragment(cache_path.path, @store_options) unless body - controller.action_has_layout = false unless @cache_layout + controller.action_has_layout = false unless cache_layout yield controller.action_has_layout = true body = controller._save_fragment(cache_path.path, @store_options) end - body = controller.render_to_string(:text => body, :layout => true) unless @cache_layout + body = controller.render_to_string(:text => body, :layout => true) unless cache_layout controller.response_body = body controller.content_type = Mime[cache_path.extension || :html] @@ -170,14 +172,14 @@ module ActionController #:nodoc: options.reverse_merge!(:format => @extension) if options.is_a?(Hash) end - path = controller.url_for(options).split(%r{://}).last + path = controller.url_for(options).split('://', 2).last @path = normalize!(path) end private def normalize!(path) path << 'index' if path[-1] == ?/ - path << ".#{extension}" if extension and !path.split('?').first.ends_with?(".#{extension}") + path << ".#{extension}" if extension and !path.split('?', 2).first.ends_with?(".#{extension}") URI.parser.unescape(path) end end diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 159f718029..307594d54a 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -99,7 +99,7 @@ module ActionController #:nodoc: # caches_page :index # # # cache the index action except for JSON requests - # caches_page :index, :if => Proc.new { |c| !c.request.format.json? } + # caches_page :index, :if => Proc.new { !request.format.json? } # # # don't gzip images # caches_page :image, :gzip => false diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index 49cf70ec21..cc1fa23935 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -54,6 +54,11 @@ module ActionController #:nodoc: class Sweeper < ActiveRecord::Observer #:nodoc: attr_accessor :controller + def initialize(*args) + super + @controller = nil + end + def before(controller) self.controller = controller callback(:before) if controller.perform_caching @@ -88,7 +93,7 @@ module ActionController #:nodoc: end def method_missing(method, *arguments, &block) - return unless @controller + return super unless @controller @controller.__send__(method, *arguments, &block) end end diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 2a8e4c575e..5b25a0d303 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -110,8 +110,8 @@ module ActionController # # Examples: # expires_in 20.minutes + # expires_in 3.hours, :public => true # expires_in 3.hours, :public => true, :must_revalidate => 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. diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index 30ddf6c16e..379ff97048 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -8,15 +8,13 @@ module ActionController #:nodoc: include ActionController::Rendering - DEFAULT_SEND_FILE_OPTIONS = { - :type => 'application/octet-stream'.freeze, - :disposition => 'attachment'.freeze, - }.freeze + DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc: + DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc: protected # Sends the file. This uses a server-appropriate method (such as X-Sendfile) # via the Rack::Sendfile middleware. The header to use is set via - # config.action_dispatch.x_sendfile_header. + # +config.action_dispatch.x_sendfile_header+. # Your server can also configure this for you by setting the X-Sendfile-Type header. # # Be careful to sanitize the path parameter if it is coming from a web @@ -74,7 +72,27 @@ module ActionController #:nodoc: self.status = options[:status] || 200 self.content_type = options[:content_type] if options.key?(:content_type) - self.response_body = File.open(path, "rb") + self.response_body = FileBody.new(path) + end + + # Avoid having to pass an open file handle as the response body. + # Rack::Sendfile will usually intercepts the response and just uses + # the path directly, so no reason to open the file. + class FileBody #:nodoc: + attr_reader :to_path + + def initialize(path) + @to_path = path + end + + # Stream the file's contents if Rack::Sendfile isn't present. + def each + File.open(to_path, 'rb') do |file| + while chunk = file.read(16384) + yield chunk + end + end + end end # Sends the given binary data to the browser. This method is similar to @@ -107,7 +125,7 @@ module ActionController #:nodoc: # # See +send_file+ for more information on HTTP Content-* headers and caching. def send_data(data, options = {}) #:doc: - send_file_headers! options.dup + send_file_headers! options render options.slice(:status, :content_type).merge(:text => data) end @@ -115,15 +133,8 @@ module ActionController #:nodoc: def send_file_headers!(options) type_provided = options.has_key?(:type) - options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) - [:type, :disposition].each do |arg| - raise ArgumentError, ":#{arg} option required" if options[arg].nil? - end - - disposition = options[:disposition] - disposition += %(; filename="#{options[:filename]}") if options[:filename] - - content_type = options[:type] + content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE) + raise ArgumentError, ":type option required" if content_type.nil? if content_type.is_a?(Symbol) extension = Mime[content_type] @@ -132,15 +143,18 @@ module ActionController #:nodoc: else if !type_provided && options[:filename] # If type wasn't provided, try guessing from file extension. - content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.tr('.','')) || content_type + content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type end self.content_type = content_type end - headers.merge!( - 'Content-Disposition' => disposition, - 'Content-Transfer-Encoding' => 'binary' - ) + disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION) + unless disposition.nil? + disposition += %(; filename="#{options[:filename]}") if options[:filename] + headers['Content-Disposition'] = disposition + end + + headers['Content-Transfer-Encoding'] = 'binary' response.sending_file = true diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index ece9ba3725..90648c37ad 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -14,8 +14,6 @@ module ActionController end class MethodNotAllowed < ActionControllerError #:nodoc: - attr_reader :allowed_methods - def initialize(*allowed_methods) super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") end @@ -30,9 +28,6 @@ module ActionController 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.' @@ -43,4 +38,7 @@ module ActionController class UnknownHttpMethod < ActionControllerError #:nodoc: end + + class UnknownFormat < ActionControllerError #:nodoc: + end end diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index a1e40fc4e0..ac12cbb625 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -44,7 +44,7 @@ module ActionController redirect_options = {:protocol => 'https://', :status => :moved_permanently} redirect_options.merge!(:host => host) if host redirect_options.merge!(:params => request.query_parameters) - flash.keep + flash.keep if respond_to?(:flash) redirect_to redirect_options end end diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index a618533d09..5bdbde9ebb 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -27,8 +27,28 @@ module ActionController self.status = status self.location = url_for(location) if location - self.content_type = Mime[formats.first] if formats + + if include_content_headers?(self.status) + self.content_type = Mime[formats.first] if formats + else + headers.delete('Content-Type') + headers.delete('Content-Length') + end + self.response_body = " " end + + private + # :nodoc: + def include_content_headers?(status) + case status + when 100..199 + false + when 204, 205, 304 + false + else + true + end + end end end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index d070eaae5d..1a4bca12d2 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -52,6 +52,7 @@ module ActionController module Helpers extend ActiveSupport::Concern + class << self; attr_accessor :helpers_path; end include AbstractController::Helpers included do diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 44d2f740e6..87225d74c1 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -229,7 +229,7 @@ module ActionController def decode_credentials(header) Hash[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair| key, value = pair.split('=', 2) - [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')] + [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').delete('\'')] end] end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 55de7e7d8e..7917926978 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -6,8 +6,6 @@ module ActionController #:nodoc: module MimeResponds extend ActiveSupport::Concern - include ActionController::ImplicitRender - included do class_attribute :responder, :mimes_for_respond_to self.responder = ActionController::Responder @@ -58,7 +56,7 @@ module ActionController #:nodoc: # Clear all mime types in <tt>respond_to</tt>. # def clear_respond_to - self.mimes_for_respond_to = ActiveSupport::OrderedHash.new.freeze + self.mimes_for_respond_to = Hash.new.freeze end end @@ -76,7 +74,7 @@ module ActionController #:nodoc: # # respond_to do |format| # format.html - # format.xml { render :xml => @people.to_xml } + # format.xml { render :xml => @people } # end # end # @@ -193,24 +191,110 @@ module ActionController #:nodoc: if collector = retrieve_collector_from_mimes(mimes, &block) response = collector.response - response ? response.call : default_render({}) + response ? response.call : render({}) end end - # respond_with wraps a resource around a responder for default representation. - # First it invokes respond_to, if a response cannot be found (ie. no block - # for the request was given and template was not available), it instantiates - # an ActionController::Responder with the controller and resource. + # For a given controller action, respond_with generates an appropriate + # response based on the mime-type requested by the client. # - # ==== Example + # If the method is called with just a resource, as in this example - # - # def index - # @users = User.all - # respond_with(@users) + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.all + # respond_with @people + # end + # end + # + # then the mime-type of the response is typically selected based on the + # request's Accept header and the set of available formats declared + # by previous calls to the controller's class method +respond_to+. Alternatively + # the mime-type can be selected by explicitly setting <tt>request.format</tt> in + # the controller. + # + # If an acceptable format is not identified, the application returns a + # '406 - not acceptable' status. Otherwise, the default response is to render + # a template named after the current action and the selected format, + # e.g. <tt>index.html.erb</tt>. If no template is available, the behavior + # depends on the selected format: + # + # * for an html response - if the request method is +get+, an exception + # is raised but for other requests such as +post+ the response + # depends on whether the resource has any validation errors (i.e. + # assuming that an attempt has been made to save the resource, + # e.g. by a +create+ action) - + # 1. If there are no errors, i.e. the resource + # was saved successfully, the response +redirect+'s to the resource + # i.e. its +show+ action. + # 2. If there are validation errors, the response + # renders a default action, which is <tt>:new</tt> for a + # +post+ request or <tt>:edit</tt> for +put+. + # Thus an example like this - + # + # respond_to :html, :xml + # + # def create + # @user = User.new(params[:user]) + # flash[:notice] = 'User was successfully created.' if @user.save + # respond_with(@user) + # end + # + # is equivalent, in the absence of <tt>create.html.erb</tt>, to - + # + # def create + # @user = User.new(params[:user]) + # respond_to do |format| + # if @user.save + # flash[:notice] = 'User was successfully created.' + # format.html { redirect_to(@user) } + # format.xml { render :xml => @user } + # else + # format.html { render :action => "new" } + # format.xml { render :xml => @user } + # end + # end + # end + # + # * for a javascript request - if the template isn't found, an exception is + # raised. + # * for other requests - i.e. data formats such as xml, json, csv etc, if + # the resource passed to +respond_with+ responds to <code>to_<format></code>, + # the method attempts to render the resource in the requested format + # directly, e.g. for an xml request, the response is equivalent to calling + # <code>render :xml => resource</code>. + # + # === Nested resources + # + # As outlined above, the +resources+ argument passed to +respond_with+ + # can play two roles. It can be used to generate the redirect url + # for successful html requests (e.g. for +create+ actions when + # no template exists), while for formats other than html and javascript + # it is the object that gets rendered, by being converted directly to the + # required format (again assuming no template exists). + # + # For redirecting successful html requests, +respond_with+ also supports + # the use of nested resources, which are supplied in the same way as + # in <code>form_for</code> and <code>polymorphic_url</code>. For example - + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.comments.build(params[:task]) + # flash[:notice] = 'Task was successfully created.' if @task.save + # respond_with(@project, @task) # end # - # It also accepts a block to be given. It's used to overwrite a default - # response: + # This would cause +respond_with+ to redirect to <code>project_task_url</code> + # instead of <code>task_url</code>. For request formats other than html or + # javascript, if multiple resources are passed in this way, it is the last + # one specified that is rendered. + # + # === Customizing response behavior + # + # Like +respond_to+, +respond_with+ may also be called with a block that + # can be used to overwrite any of the default responses, e.g. - # # def create # @user = User.new(params[:user]) @@ -221,13 +305,24 @@ module ActionController #:nodoc: # end # end # - # All options given to respond_with are sent to the underlying responder, - # except for the option :responder itself. Since the responder interface - # is quite simple (it just needs to respond to call), you can even give - # a proc to it. - # - # In order to use respond_with, first you need to declare the formats your - # controller responds to in the class level with a call to <tt>respond_to</tt>. + # The argument passed to the block is an ActionController::MimeResponds::Collector + # object which stores the responses for the formats defined within the + # block. Note that formats with responses defined explicitly in this way + # do not have to first be declared using the class method +respond_to+. + # + # Also, a hash passed to +respond_with+ immediately after the specified + # resource(s) is interpreted as a set of options relevant to all + # formats. Any option accepted by +render+ can be used, e.g. + # respond_with @people, :status => 200 + # However, note that these options are ignored after an unsuccessful attempt + # to save a resource, e.g. when automatically rendering <tt>:new</tt> + # after a post request. + # + # Two additional options are relevant specifically to +respond_with+ - + # 1. <tt>:location</tt> - overwrites the default redirect location used after + # a successful html +post+ request. + # 2. <tt>:action</tt> - overwrites the default render action used after an + # unsuccessful html +post+ request. # def respond_with(*resources, &block) raise "In order to use respond_with, first you need to declare the formats your " << @@ -235,16 +330,8 @@ module ActionController #:nodoc: if collector = retrieve_collector_from_mimes(&block) options = resources.size == 1 ? {} : resources.extract_options! - - if defined_response = collector.response - if action = options.delete(:action) - render :action => action - else - defined_response.call - end - else - (options.delete(:responder) || self.class.responder).call(self, resources, options) - end + options[:default_response] = collector.response + (options.delete(:responder) || self.class.responder).call(self, resources, options) end end @@ -269,8 +356,12 @@ module ActionController #:nodoc: end end - # Collects mimes and return the response for the negotiated format. Returns - # nil if :not_acceptable was sent to the client. + # Returns a Collector object containing the appropriate mime-type response + # for the current request, based on the available responses defined by a block. + # In typical usage this is the block passed to +respond_with+ or +respond_to+. + # + # Sends :not_acceptable to the client and returns nil if no suitable format + # is available. # def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc: mimes ||= collect_mimes_from_class_level @@ -281,14 +372,37 @@ module ActionController #:nodoc: if format self.content_type ||= format.to_s lookup_context.formats = [format.to_sym] + lookup_context.rendered_format = lookup_context.formats.first collector else - head :not_acceptable - nil + raise ActionController::UnknownFormat end end - class Collector #:nodoc: + # A container for responses available from the current controller for + # requests for different mime-types sent to a particular action. + # + # The public controller methods +respond_with+ and +respond_to+ may be called + # with a block that is used to define responses to different mime-types, e.g. + # for +respond_to+ : + # + # respond_to do |format| + # format.html + # format.xml { render :xml => @people } + # end + # + # In this usage, the argument passed to the block (+format+ above) is an + # instance of the ActionController::MimeResponds::Collector class. This + # object serves as a container in which available responses can be stored by + # calling any of the dynamically generated, mime-type-specific methods such + # as +html+, +xml+ etc on the Collector. Each response is represented by a + # corresponding block if present. + # + # A subsequent call to #negotiate_format(request) will enable the Collector + # to determine which specific mime-type it should respond with for the current + # request, with this response then being accessible by calling #response. + # + class Collector include AbstractController::Collector attr_accessor :order, :format diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index fa760f2658..1f52c164de 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -48,7 +48,7 @@ module ActionController # method attribute_names. # # If you're going to pass the parameters to an +ActiveModel+ object (such as - # +User.new(params[:user])+), you might consider passing the model class to + # <tt>User.new(params[:user])</tt>), you might consider passing the model class to # the method instead. The +ParamsWrapper+ will actually try to determine the # list of attribute names from the model and only wrap those attributes: # @@ -66,7 +66,7 @@ module ActionController # class Admin::UsersController < ApplicationController # end # - # will try to check if +Admin::User+ or +User+ model exists, and use it to + # will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to # determine the wrapper key respectively. If both models don't exist, # it will then fallback to use +user+ as the key. module ParamsWrapper @@ -166,8 +166,9 @@ module ActionController unless options[:include] || options[:exclude] model ||= _default_wrap_model - if model.respond_to?(:accessible_attributes) && model.accessible_attributes.present? - options[:include] = model.accessible_attributes.to_a + role = options.fetch(:as, :default) + if model.respond_to?(:accessible_attributes) && model.accessible_attributes(role).present? + options[:include] = model.accessible_attributes(role).to_a elsif model.respond_to?(:attribute_names) && model.attribute_names.present? options[:include] = model.attribute_names end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index b07742e0e1..5e7bd44562 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -45,6 +45,16 @@ module ActionController # integer, or a symbol representing the downcased, underscored and symbolized description. # Note that the status code must be a 3xx HTTP code, or redirection will not occur. # + # If you are using XHR requests other than GET or POST and redirecting after the + # request then some browsers will follow the redirect using the original request + # method. This may lead to undesirable behavior such as a double DELETE. To work + # around this you can return a <tt>303 See Other</tt> status code which will be + # followed using a GET request. + # + # Examples: + # redirect_to posts_url, :status => :see_other + # redirect_to :action => 'index', :status => 303 + # # It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names # +alert+ and +notice+ as well as a general purpose +flash+ bucket. # @@ -93,7 +103,7 @@ module ActionController _compute_redirect_to_location options.call else url_for(options) - end.gsub(/[\r\n]/, '') + end.delete("\0\r\n") end end end diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 6e9ce450ac..4a0c1c7dd7 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -91,9 +91,14 @@ module ActionController add :json do |json, options| json = json.to_json(options) unless json.kind_of?(String) - json = "#{options[:callback]}(#{json})" unless options[:callback].blank? - self.content_type ||= Mime::JSON - json + + if options[:callback].present? + self.content_type ||= Mime::JS + "#{options[:callback]}(#{json})" + else + self.content_type ||= Mime::JSON + json + end end add :js do |js, options| diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index afa9243f02..0bff1825d9 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -17,7 +17,6 @@ module ActionController #:nodoc: # CSRF protection is turned on with the <tt>protect_from_forgery</tt> method, # which checks the token and resets the session if it doesn't match what was expected. # A call to this method is generated for new \Rails applications by default. - # You can customize the error message by editing public/422.html. # # The token parameter is named <tt>authenticity_token</tt> by default. The name and # value of this token must be added to every layout that renders forms by including @@ -37,6 +36,10 @@ module ActionController #:nodoc: config_accessor :request_forgery_protection_token self.request_forgery_protection_token ||= :authenticity_token + # Controls how unverified request will be handled + config_accessor :request_forgery_protection_method + self.request_forgery_protection_method ||= :reset_session + # Controls whether request forgery protection is turned on or not. Turned off by default only in test mode. config_accessor :allow_forgery_protection self.allow_forgery_protection = true if allow_forgery_protection.nil? @@ -64,8 +67,10 @@ module ActionController #:nodoc: # Valid Options: # # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified. + # * <tt>:with</tt> - Set the method to handle unverified request. Valid values: <tt>:exception</tt> and <tt>:reset_session</tt> (default). def protect_from_forgery(options = {}) self.request_forgery_protection_token ||= :authenticity_token + self.request_forgery_protection_method = options.delete(:with) if options.key?(:with) prepend_before_filter :verify_authenticity_token, options end end @@ -80,9 +85,19 @@ module ActionController #:nodoc: end # This is the method that defines the application behavior when a request is found to be unverified. - # By default, \Rails resets the session when it finds an unverified request. + # By default, \Rails uses <tt>request_forgery_protection_method</tt> when it finds an unverified request: + # + # * <tt>:reset_session</tt> - Resets the session. + # * <tt>:exception</tt>: - Raises ActionController::InvalidAuthenticityToken exception. def handle_unverified_request - reset_session + case request_forgery_protection_method + when :exception + raise ActionController::InvalidAuthenticityToken + when :reset_session + reset_session + else + raise ArgumentError, 'Invalid request forgery protection method, use :exception or :reset_session' + end end # Returns true or false if a request is verified. Checks: diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index ccda01ed44..83407846dc 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -63,7 +63,7 @@ module ActionController #:nodoc: # # def create # @project = Project.find(params[:project_id]) - # @task = @project.comments.build(params[:task]) + # @task = @project.tasks.build(params[:task]) # flash[:notice] = 'Task was successfully created.' if @task.save # respond_with(@project, @task) # end @@ -130,6 +130,7 @@ module ActionController #:nodoc: @resources = resources @options = options @action = options.delete(:action) + @default_response = options.delete(:default_response) end delegate :head, :render, :redirect_to, :to => :controller @@ -172,7 +173,7 @@ module ActionController #:nodoc: # responds to :to_format and display it. # def to_format - if get? || !has_errors? + if get? || !has_errors? || response_overridden? default_render else display_errors @@ -226,7 +227,11 @@ module ActionController #:nodoc: # controller. # def default_render - controller.default_render(options) + if @default_response + @default_response.call(options) + else + controller.default_render(options) + end end # Display is just a shortcut to render a resource with the current format. @@ -274,5 +279,9 @@ module ActionController #:nodoc: def json_resource_errors {:errors => resource.errors} end + + def response_overridden? + @default_response.present? + end end end diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb deleted file mode 100644 index 91d89ff9a4..0000000000 --- a/actionpack/lib/action_controller/metal/session_management.rb +++ /dev/null @@ -1,9 +0,0 @@ -module ActionController #:nodoc: - module SessionManagement #:nodoc: - extend ActiveSupport::Concern - - module ClassMethods - - end - end -end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index e9783e6919..eeb37db2e7 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -139,17 +139,17 @@ module ActionController #:nodoc: # session or flash after the template starts rendering will not propagate # to the client. # - # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+ + # If you try to modify cookies, session or flash, an <tt>ActionDispatch::ClosedError</tt> # will be raised, showing those objects are closed for modification. # # == Middlewares # # Middlewares that need to manipulate the body won't work with streaming. # You should disable those middlewares whenever streaming in development - # or production. For instance, +Rack::Bug+ won't work when streaming as it + # or production. For instance, <tt>Rack::Bug</tt> won't work when streaming as it # needs to inject contents in the HTML body. # - # Also +Rack::Cache+ won't work with streaming as it does not support + # Also <tt>Rack::Cache</tt> won't work with streaming as it does not support # streaming bodies yet. Whenever streaming Cache-Control is automatically # set to "no-cache". # @@ -162,7 +162,7 @@ module ActionController #:nodoc: # Currently, when an exception happens in development or production, Rails # will automatically stream to the client: # - # "><script type="text/javascript">window.location = "/500.html"</script></html> + # "><script>window.location = "/500.html"</script></html> # # The first two characters (">) are required in case the exception happens # while rendering attributes for a given tag. You can check the real cause diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 0b40b1fc4c..8e7b56dbcc 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -1,25 +1,25 @@ -# Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing -# the <tt>_routes</tt> method. Otherwise, an exception will be raised. -# -# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define -# url options like the +host+. In order to do so, this module requires the host class -# to implement +env+ and +request+, which need to be a Rack-compatible. -# -# Example: -# -# class RootUrl -# include ActionController::UrlFor -# include Rails.application.routes.url_helpers -# -# delegate :env, :request, :to => :controller -# -# def initialize(controller) -# @controller = controller -# @url = root_path # named route from the application. -# end -# end -# module ActionController + # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing + # the <tt>_routes</tt> method. Otherwise, an exception will be raised. + # + # In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define + # url options like the +host+. In order to do so, this module requires the host class + # to implement +env+ and +request+, which need to be a Rack-compatible. + # + # Example: + # + # class RootUrl + # include ActionController::UrlFor + # include Rails.application.routes.url_helpers + # + # delegate :env, :request, :to => :controller + # + # def initialize(controller) + # @controller = controller + # @url = root_path # named route from the application. + # end + # end + # module UrlFor extend ActiveSupport::Concern @@ -42,6 +42,5 @@ module ActionController @_url_options end end - end end diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 3e170d7872..851a2c4aee 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -3,33 +3,32 @@ require "action_controller" require "action_dispatch/railtie" require "action_view/railtie" require "abstract_controller/railties/routes_helpers" -require "action_controller/railties/paths" +require "action_controller/railties/helpers" module ActionController class Railtie < Rails::Railtie #:nodoc: config.action_controller = ActiveSupport::OrderedOptions.new - initializer "action_controller.logger" do - ActiveSupport.on_load(:action_controller) { self.logger ||= Rails.logger } - end - - initializer "action_controller.initialize_framework_caches" do - ActiveSupport.on_load(:action_controller) { self.cache_store ||= Rails.cache } - end - initializer "action_controller.assets_config", :group => :all do |app| app.config.action_controller.assets_dir ||= app.config.paths["public"].first end + initializer "action_controller.set_helpers_path" do |app| + ActionController::Helpers.helpers_path = app.helpers_paths + end + initializer "action_controller.set_configs" do |app| paths = app.config.paths options = app.config.action_controller + options.logger ||= Rails.logger + options.cache_store ||= Rails.cache + options.javascripts_dir ||= paths["public/javascripts"].first options.stylesheets_dir ||= paths["public/stylesheets"].first options.page_cache_directory ||= paths["public"].first - # make sure readers methods get compiled + # Ensure readers methods get compiled options.asset_path ||= app.config.asset_path options.asset_host ||= app.config.asset_host options.relative_url_root ||= app.config.relative_url_root @@ -37,8 +36,16 @@ module ActionController ActiveSupport.on_load(:action_controller) do include app.routes.mounted_helpers extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) - extend ::ActionController::Railties::Paths.with(app) - options.each { |k,v| send("#{k}=", v) } + extend ::ActionController::Railties::Helpers + + options.each do |k,v| + k = "#{k}=" + if respond_to?(k) + send(k, v) + elsif !Base.respond_to?(k) + raise "Invalid option key: #{k}" + end + end end end diff --git a/actionpack/lib/action_controller/railties/helpers.rb b/actionpack/lib/action_controller/railties/helpers.rb new file mode 100644 index 0000000000..3985c6b273 --- /dev/null +++ b/actionpack/lib/action_controller/railties/helpers.rb @@ -0,0 +1,22 @@ +module ActionController + module Railties + module Helpers + def inherited(klass) + super + return unless klass.respond_to?(:helpers_path=) + + if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) } + paths = namespace.railtie_helpers_paths + else + paths = ActionController::Helpers.helpers_path + end + + klass.helpers_path = paths + + if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers + klass.helper :all + end + end + end + end +end diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb deleted file mode 100644 index bbe63149ad..0000000000 --- a/actionpack/lib/action_controller/railties/paths.rb +++ /dev/null @@ -1,25 +0,0 @@ -module ActionController - module Railties - module Paths - def self.with(app) - Module.new do - define_method(:inherited) do |klass| - super(klass) - - if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) } - paths = namespace.railtie_helpers_paths - else - paths = app.helpers_paths - end - - klass.helpers_path = paths - - if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers - klass.helper :all - end - end - end - end - end - end -end diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 9c38ff44d8..e7af3f5b8d 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -2,8 +2,8 @@ require 'active_support/core_ext/module' module ActionController # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or - # Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate - # the view actions to a higher logical level. Example: + # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to + # a higher logical level. Example: # # # routes # resources :posts @@ -53,6 +53,7 @@ module ActionController # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id: # # dom_id(Post.find(45), :edit) # => "edit_post_45" + # dom_id(Post.new, :custom) # => "custom_post" def dom_id(record, prefix = nil) if record_id = record_key_for_dom_id(record) "#{dom_class(record, prefix)}#{JOIN}#{record_id}" @@ -74,12 +75,7 @@ module ActionController def record_key_for_dom_id(record) record = record.to_model if record.respond_to?(:to_model) key = record.to_key - key ? sanitize_dom_id(key.join('_')) : key - end - - # Replaces characters that are invalid in HTML DOM ids with valid ones. - def sanitize_dom_id(candidate_id) - candidate_id # TODO implement conversion to valid DOM id values + key ? key.join('_') : key end end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 3509e74d5e..ad02375f12 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -20,20 +20,25 @@ module ActionController ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload| path = payload[:layout] - @layouts[path] += 1 + if path + @layouts[path] += 1 + if path =~ /^layouts\/(.*)/ + @layouts[$1] += 1 + end + end end ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload| path = payload[:virtual_path] next unless path partial = path =~ /^.*\/_[^\/]*$/ + if partial @partials[path] += 1 @partials[path.split("/").last] += 1 - @templates[path] += 1 - else - @templates[path] += 1 end + + @templates[path] += 1 end end @@ -56,6 +61,18 @@ module ActionController # # assert that the "new" view template was rendered # assert_template "new" # + # # assert that the exact template "admin/posts/new" was rendered + # assert_template %r{\Aadmin/posts/new\Z} + # + # # assert that the layout 'admin' was rendered + # assert_template :layout => 'admin' + # assert_template :layout => 'layouts/admin' + # assert_template :layout => :admin + # + # # assert that no layout was rendered + # assert_template :layout => nil + # assert_template :layout => false + # # # assert that the "_customer" partial was rendered twice # assert_template :partial => '_customer', :count => 2 # @@ -69,12 +86,16 @@ module ActionController # assert_template :partial => '_customer', :locals => { :customer => @customer } # def assert_template(options = {}, message = nil) + # Force body to be read in case the + # template is being streamed + response.body + case options - when NilClass, String, Symbol + when NilClass, String, Symbol, Regexp options = options.to_s if Symbol === options rendered = @templates msg = message || sprintf("expecting <%s> but rendering with <%s>", - options, rendered.keys) + options.inspect, rendered.keys) assert_block(msg) do if options rendered.any? { |t,num| t.match(options) } @@ -83,16 +104,17 @@ module ActionController end end when Hash - if expected_layout = options[:layout] + if options.key?(:layout) + expected_layout = options[:layout] msg = message || sprintf("expecting layout <%s> but action rendered <%s>", expected_layout, @layouts.keys) case expected_layout - when String - assert_includes @layouts.keys, expected_layout, msg + when String, Symbol + assert_includes @layouts.keys, expected_layout.to_s, msg when Regexp assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg) - when nil + when nil, false assert(@layouts.empty?, msg) end end @@ -113,10 +135,12 @@ module ActionController options[:partial], @partials.keys) assert_includes @partials, expected_partial, msg end - else + elsif options.key?(:partial) assert @partials.empty?, "Expected no partials to be rendered" end + else + raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil" end end end @@ -131,9 +155,6 @@ module ActionController class Result < ::Array #:nodoc: def to_s() join '/' end - def self.new_escaped(strings) - new strings.collect {|str| uri_parser.unescape str} - end end def assign_parameters(routes, controller_path, action, parameters = {}) @@ -141,17 +162,23 @@ module ActionController extra_keys = routes.extra_keys(parameters) non_path_parameters = get? ? query_parameters : request_parameters parameters.each do |key, value| - if value.is_a? Fixnum - value = value.to_s - elsif value.is_a? Array - value = Result.new(value.map { |v| v.is_a?(String) ? v.dup : v }) - elsif value.is_a? String + if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?)) + value = value.map{ |v| v.duplicable? ? v.dup : v } + elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? }) + value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }] + elsif value.frozen? && value.duplicable? value = value.dup end if extra_keys.include?(key.to_sym) non_path_parameters[key] = value else + if value.is_a?(Array) + value = Result.new(value.map(&:to_param)) + else + value = value.to_param + end + path_parameters[key.to_s] = value end end @@ -447,7 +474,7 @@ module ActionController # Ensure that numbers and symbols passed as params are converted to # proper params, as is the case when engaging rack. - parameters = paramify_values(parameters) + parameters = paramify_values(parameters) if html_format?(parameters) @request.recycle! @response.recycle! @@ -460,17 +487,15 @@ module ActionController parameters ||= {} controller_class_name = @controller.class.anonymous? ? - "anonymous_controller" : + "anonymous" : @controller.class.name.underscore.sub(/_controller$/, '') @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters) - @request.session = ActionController::TestSession.new(session) if session + @request.session.update(session) if session @request.session["flash"] = @request.flash.update(flash || {}) - @request.session["flash"].sweep @controller.request = @request - @controller.params.merge!(parameters) build_request_uri(action, parameters) @controller.class.class_eval { include Testing } @controller.recycle! @@ -496,11 +521,6 @@ module ActionController end end - # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local - def rescue_action_in_public! - @request.remote_addr = '208.77.188.166' # example.com - end - included do include ActionController::TemplateAssertions include ActionDispatch::Assertions @@ -546,6 +566,12 @@ module ActionController @request.env["QUERY_STRING"] = query_string || "" end end + + def html_format?(parameters) + return true unless parameters.is_a?(Hash) + format = Mime[parameters[:format]] + format.nil? || format.html? + end end # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index 24ffc28710..114b0e73c9 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -5,6 +5,7 @@ require 'active_support/core_ext/class/attribute' module HTML class Sanitizer def sanitize(text, options = {}) + validate_options(options) return text unless sanitizeable?(text) tokenize(text, options).join end @@ -27,6 +28,16 @@ module HTML def process_node(node, result, options) result << node.to_s end + + def validate_options(options) + if options[:tags] && !options[:tags].is_a?(Enumerable) + raise ArgumentError, "You should pass :tags as an Enumerable" + end + + if options[:attributes] && !options[:attributes].is_a?(Enumerable) + raise ArgumentError, "You should pass :attributes as an Enumerable" + end + end end class FullSanitizer < Sanitizer @@ -88,7 +99,7 @@ module HTML self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto feed svn urn aim rsync tag ssh sftp rtsp afs)) - # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept. + # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept. self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse border-color border-left-color border-right-color border-top-color clear color cursor direction display elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height |