diff options
Diffstat (limited to 'actionpack/lib/action_controller')
21 files changed, 205 insertions, 152 deletions
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index 59ec197347..80901b8bf3 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -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] diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 307594d54a..dd4eddbe9a 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -60,7 +60,8 @@ module ActionController #:nodoc: end module ClassMethods - # Expires the page that was cached with the +path+ as a key. Example: + # Expires the page that was cached with the +path+ as a key. + # # expire_page "/lists/show" def expire_page(path) return unless perform_caching @@ -72,7 +73,8 @@ module ActionController #:nodoc: end end - # Manually cache the +content+ in the key determined by +path+. Example: + # Manually cache the +content+ in the key determined by +path+. + # # cache_page "I'm the cached content", "/lists/show" def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION) return unless perform_caching @@ -93,8 +95,6 @@ module ActionController #:nodoc: # # You can also pass a :gzip option to override the class configuration one. # - # Usage: - # # # cache the index action # caches_page :index # @@ -142,7 +142,8 @@ module ActionController #:nodoc: end end - # Expires the page that was cached with the +options+ as a key. Example: + # Expires the page that was cached with the +options+ as a key. + # # expire_page :controller => "lists", :action => "show" def expire_page(options = {}) return unless self.class.perform_caching @@ -161,7 +162,8 @@ module ActionController #:nodoc: end # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used. - # If no options are provided, the url of the current request being handled is used. Example: + # If no options are provided, the url of the current request being handled is used. + # # cache_page "I'm the cached content", :controller => "lists", :action => "show" def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION) return unless self.class.perform_caching && caching_allowed? diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index bb176ca3f9..cc1fa23935 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -93,7 +93,7 @@ module ActionController #:nodoc: end def method_missing(method, *arguments, &block) - super unless @controller + return super unless @controller @controller.__send__(method, *arguments, &block) end end diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 4c76f4c43b..11aa393bf9 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -20,7 +20,7 @@ module ActionController status = payload[:status] if status.nil? && payload[:exception].present? - status = Rack::Utils.status_code(ActionDispatch::ExceptionWrapper.new({}, payload[:exception]).status_code) + status = ActionDispatch::ExceptionWrapper.new({}, payload[:exception]).status_code end message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration message << " (#{additions.join(" | ")})" unless additions.blank? diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 5b25a0d303..2193dde667 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -108,7 +108,6 @@ module ActionController # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a <tt>private</tt> instruction, so that # intermediate caches must not cache the response. # - # Examples: # expires_in 20.minutes # expires_in 3.hours, :public => true # expires_in 3.hours, :public => true, :must_revalidate => true 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 505b2ac609..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 @@ -40,4 +38,7 @@ module ActionController class UnknownHttpMethod < ActionControllerError #:nodoc: end + + class UnknownFormat < ActionControllerError #:nodoc: + end end diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index a618533d09..2fcd933d32 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -20,6 +20,7 @@ module ActionController options, status = status, nil if status.is_a?(Hash) status ||= options.delete(:status) || :ok location = options.delete(:location) + content_type = options.delete(:content_type) options.each do |key, value| headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s @@ -27,8 +28,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 = 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 1a4bca12d2..86d061e3b7 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -16,7 +16,6 @@ module ActionController # Additional helpers can be specified using the +helper+ class method in ActionController::Base 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 # a \Time object is blank: # diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 44d2f740e6..57bb0e2a32 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -2,8 +2,9 @@ require 'base64' require 'active_support/core_ext/object/blank' module ActionController + # Makes it dead easy to do HTTP Basic, Digest and Token authentication. module HttpAuthentication - # Makes it dead easy to do HTTP \Basic and \Digest authentication. + # Makes it dead easy to do HTTP \Basic authentication. # # === Simple \Basic example # @@ -60,47 +61,6 @@ module ActionController # # 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 - # - # === Notes - # - # 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. - # - # In rare instances, web servers or front proxies strip authorization headers before - # they reach your application. You can debug this situation by logging all environment - # variables, and check for HTTP_AUTHORIZATION, amongst others. module Basic extend self @@ -155,6 +115,48 @@ module ActionController end end + # Makes it dead easy to do HTTP \Digest authentication. + # + # === 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 + # + # === Notes + # + # 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. + # + # In rare instances, web servers or front proxies strip authorization headers before + # they reach your application. You can debug this situation by logging all environment + # variables, and check for HTTP_AUTHORIZATION, amongst others. module Digest extend self @@ -229,7 +231,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 fbb5d01e86..0b800c3c62 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -16,8 +16,6 @@ module ActionController #:nodoc: # Defines mime types that are rendered by default when invoking # <tt>respond_with</tt>. # - # Examples: - # # respond_to :html, :xml, :json # # Specifies that all actions in the controller respond to requests @@ -74,7 +72,7 @@ module ActionController #:nodoc: # # respond_to do |format| # format.html - # format.xml { render :xml => @people.to_xml } + # format.xml { render :xml => @people } # end # end # @@ -185,7 +183,6 @@ module ActionController #:nodoc: # end # # Be sure to check respond_with and respond_to documentation for more examples. - # def respond_to(*mimes, &block) raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? @@ -323,7 +320,6 @@ module ActionController #:nodoc: # 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 " << "controller responds to in the class level" if self.class.mimes_for_respond_to.empty? @@ -339,7 +335,6 @@ module ActionController #:nodoc: # 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_s @@ -362,7 +357,6 @@ module ActionController #:nodoc: # # 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 collector = Collector.new(mimes) @@ -375,8 +369,7 @@ module ActionController #:nodoc: lookup_context.rendered_format = lookup_context.formats.first collector else - head :not_acceptable - nil + raise ActionController::UnknownFormat end end @@ -389,7 +382,7 @@ module ActionController #:nodoc: # # respond_to do |format| # format.html - # format.xml { render :xml => @people.to_xml } + # format.xml { render :xml => @people } # end # # In this usage, the argument passed to the block (+format+ above) is an @@ -402,7 +395,6 @@ module ActionController #:nodoc: # 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 3ffb7ef426..ee0e69d87c 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -24,7 +24,6 @@ module ActionController # * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places. # Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt> # - # Examples: # redirect_to :action => "show", :id => 5 # redirect_to post # redirect_to "http://www.rubyonrails.org" @@ -35,7 +34,6 @@ module ActionController # # The redirection happens as a "302 Moved" header unless otherwise specified. # - # Examples: # redirect_to post_url(@post), :status => :found # redirect_to :action=>'atom', :status => :moved_permanently # redirect_to post_url(@post), :status => 301 @@ -45,10 +43,18 @@ 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. + # + # 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. # - # Examples: # redirect_to post_url(@post), :alert => "Watch it, mister!" # redirect_to post_url(@post), :status=> :found, :notice => "Pay attention to the road" # redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id } @@ -59,6 +65,7 @@ module ActionController def redirect_to(options = {}, response_status = {}) #:doc: raise ActionControllerError.new("Cannot redirect to nil!") unless options raise AbstractController::DoubleRenderError if response_body + logger.debug { "Redirected by #{caller(1).first rescue "unknown"}" } if logger self.status = _extract_redirect_to_status(options, response_status) self.location = _compute_redirect_to_location(options) @@ -93,7 +100,7 @@ module ActionController _compute_redirect_to_location options.call else url_for(options) - end.gsub(/[\0\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..1927c8bdc7 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -49,7 +49,6 @@ module ActionController # is the value paired with its key and the second is the remaining # hash of options passed to +render+. # - # === Example # Create a csv renderer: # # ActionController::Renderers.add :csv do |obj, options| @@ -91,9 +90,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 3081c14c09..95b0e99ed5 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 @@ -52,8 +51,6 @@ module ActionController #:nodoc: 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 # diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 1e8990495c..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 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 8e7b56dbcc..e28c05cc2d 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -6,8 +6,6 @@ module ActionController # 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 @@ -19,7 +17,6 @@ module ActionController # @url = root_path # named route from the application. # end # end - # module UrlFor extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 18dda978b3..16a5decc62 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -3,7 +3,7 @@ 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 # 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: + # a higher logical level. # # # routes # resources :posts @@ -30,7 +30,7 @@ module ActionController JOIN = '_'.freeze NEW = 'new'.freeze - # The DOM class convention is to use the singular form of an object or class. Examples: + # The DOM class convention is to use the singular form of an object or class. # # dom_class(post) # => "post" # dom_class(Person) # => "person" @@ -45,7 +45,7 @@ module ActionController end # The DOM id convention is to use the singular form of an object or class with the id following an underscore. - # If no id is found, prefix with "new_" instead. Examples: + # If no id is found, prefix with "new_" instead. # # dom_id(Post.find(45)) # => "post_45" # dom_id(Post.new) # => "new_post" @@ -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 9bd2e622ad..028a8d3fba 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 @@ -51,14 +56,21 @@ module ActionController # Asserts that the request was rendered with the appropriate template file or partials. # - # ==== Examples - # # # 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 # @@ -70,7 +82,6 @@ module ActionController # # # assert that the "_customer" partial was rendered with a specific object # 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 @@ -82,24 +93,25 @@ module ActionController rendered = @templates msg = message || sprintf("expecting <%s> but rendering with <%s>", options.inspect, rendered.keys) - assert_block(msg) do + matches_template = if options rendered.any? { |t,num| t.match(options) } else @templates.blank? end - end + assert matches_template, msg 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 @@ -120,7 +132,7 @@ 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 @@ -140,9 +152,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 = {}) @@ -150,17 +159,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 @@ -332,7 +347,6 @@ module ActionController # == \Testing named routes # # If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case. - # Example: # # assert_redirected_to page_url(:title => 'foo') class TestCase < ActiveSupport::TestCase @@ -351,12 +365,11 @@ module ActionController module ClassMethods # Sets the controller class name. Useful if the name can't be inferred from test class. - # Normalizes +controller_class+ before using. Examples: + # Normalizes +controller_class+ before using. # # tests WidgetController # tests :widget # tests 'widget' - # def tests(controller_class) case controller_class when String, Symbol @@ -456,7 +469,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! @@ -469,14 +482,13 @@ 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 build_request_uri(action, parameters) @@ -549,6 +561,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 e9b50ff8ce..6b269e7a31 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -1,6 +1,7 @@ require 'set' require 'cgi' require 'active_support/core_ext/class/attribute' +require 'active_support/core_ext/class/attribute_accessors' module HTML class Sanitizer @@ -99,7 +100,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 |