diff options
Diffstat (limited to 'actionpack')
22 files changed, 122 insertions, 83 deletions
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc index ccd0193515..29a7fcf0f0 100644 --- a/actionpack/README.rdoc +++ b/actionpack/README.rdoc @@ -10,7 +10,7 @@ It consists of several modules: * Action Dispatch, which parses information about the web request, handles routing as defined by the user, and does advanced processing related to HTTP - such as MIME-type negotiation, decoding parameters in POST/PUT bodies, + such as MIME-type negotiation, decoding parameters in POST, PATCH, or PUT bodies, handling HTTP caching logic, cookies and sessions. * Action Controller, which provides a base controller class that can be diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 76b0048529..cf2cda039d 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -87,7 +87,7 @@ module ActionController end def view_cache_dependencies - self.class._view_cache_dependencies.map { |dep| instance_exec &dep }.compact + self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact end protected diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index e59213806f..d04fbae150 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -82,7 +82,7 @@ module ActionController #:nodoc: # (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]) + # @company = Company.find_or_create_by(name: params[:company][:name]) # @person = @company.people.create(params[:person]) # # redirect_to(person_list_url) @@ -92,7 +92,7 @@ module ActionController #:nodoc: # # def create # company = params[:person].delete(:company) - # @company = Company.find_or_create_by_name(company[:name]) + # @company = Company.find_or_create_by(name: company[:name]) # @person = @company.people.create(params[:person]) # # respond_to do |format| @@ -120,7 +120,7 @@ module ActionController #:nodoc: # 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]) + # @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): @@ -227,7 +227,7 @@ module ActionController #:nodoc: # 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+. + # +post+ request or <tt>:edit</tt> for +patch+ or +put+. # Thus an example like this - # # respond_to :html, :xml diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 091facfd8d..13ec55fe92 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -32,7 +32,7 @@ module ActionController # redirect_to :back # redirect_to proc { edit_post_url(@post) } # - # The redirection happens as a "302 Moved" header unless otherwise specified. + # The redirection happens as a "302 Found" header unless otherwise specified. # # redirect_to post_url(@post), status: :found # redirect_to action: 'atom', status: :moved_permanently diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 0b3c438ec2..73e9b5660d 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -26,7 +26,7 @@ module ActionController #:nodoc: # # class PostsController # def index - # @posts = Post.scoped + # @posts = Post.all # render stream: true # end # end @@ -51,9 +51,9 @@ module ActionController #:nodoc: # # def dashboard # # Allow lazy execution of the queries - # @posts = Post.scoped - # @pages = Page.scoped - # @articles = Article.scoped + # @posts = Post.all + # @pages = Page.all + # @articles = Article.all # render stream: true # end # diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 1d286524f4..941b6e210f 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -44,7 +44,7 @@ module ActionController # # => #<Person id: 1, name: "Francesco", age: 22, role: "user"> # # It provides a +permit_all_parameters+ option that controls the top-level - # behaviour of new instances. If it's +true+, all the parameters will be + # behavior of new instances. If it's +true+, all the parameters will be # permitted by default. The default value for +permit_all_parameters+ # option is +false+. # diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 03b0f25f29..70e0a820c4 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -4,7 +4,7 @@ module ActionController module RecordIdentifier MESSAGE = 'method will no longer be included by default in controllers since Rails 4.1. ' + 'If you would like to use it in controllers, please include ' + - 'ActionView::RecodIdentifier module.' + 'ActionView::RecordIdentifier module.' def dom_id(record, prefix = nil) ActiveSupport::Deprecation.warn('dom_id ' + MESSAGE) diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 02ab49b44e..289e204ac8 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/object/duplicable' +require 'action_dispatch/http/parameter_filter' module ActionDispatch module Http diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 452809a689..7b04d6e851 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -3,6 +3,14 @@ require 'stringio' require 'active_support/inflector' require 'action_dispatch/http/headers' require 'action_controller/metal/exceptions' +require 'rack/request' +require 'action_dispatch/http/cache' +require 'action_dispatch/http/mime_negotiation' +require 'action_dispatch/http/parameters' +require 'action_dispatch/http/filter_parameters' +require 'action_dispatch/http/upload' +require 'action_dispatch/http/url' +require 'active_support/core_ext/array/conversions' module ActionDispatch class Request < Rack::Request @@ -276,15 +284,14 @@ module ActionDispatch LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip end - protected - # Remove nils from the params hash def deep_munge(hash) - hash.each_value do |v| + hash.each do |k, v| case v when Array v.grep(Hash) { |x| deep_munge(x) } v.compact! + hash[k] = nil if v.empty? when Hash deep_munge(v) end @@ -293,6 +300,8 @@ module ActionDispatch hash end + protected + def parse_query(qs) deep_munge(super) end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 121a11c8e1..6ecbb03784 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -15,7 +15,7 @@ module ActionDispatch # 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: + # Examples of writing: # # # Sets a simple session cookie. # # This cookie will be deleted when the user's browser is closed. @@ -38,7 +38,7 @@ module ActionDispatch # # You can also chain these methods: # cookies.permanent.signed[:login] = "XJ-122" # - # Examples for reading: + # Examples of reading: # # cookies[:user_name] # => "david" # cookies.size # => 2 diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index f24e9b8e18..7e56feb90a 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -59,12 +59,12 @@ module ActionDispatch @flash[k] end - # Convenience accessor for flash.now[:alert]= + # Convenience accessor for <tt>flash.now[:alert]=</tt>. def alert=(message) self[:alert] = message end - # Convenience accessor for flash.now[:notice]= + # Convenience accessor for <tt>flash.now[:notice]=</tt>. def notice=(message) self[:notice] = message end @@ -82,7 +82,7 @@ module ActionDispatch else new end - + flash.tap(&:sweep) end @@ -169,6 +169,14 @@ module ActionDispatch # 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>. + # + # Also, brings two convenience accessors: + # + # flash.now.alert = "Beware now!" + # # Equivalent to flash.now[:alert] = "Beware now!" + # + # flash.now.notice = "Good luck now!" + # # Equivalent to flash.now[:notice] = "Good luck now!" def now @now ||= FlashNow.new(self) end @@ -199,22 +207,22 @@ module ActionDispatch @discard.replace @flashes.keys end - # Convenience accessor for flash[:alert] + # Convenience accessor for <tt>flash[:alert]</tt>. def alert self[:alert] end - # Convenience accessor for flash[:alert]= + # Convenience accessor for <tt>flash[:alert]=</tt>. def alert=(message) self[:alert] = message end - # Convenience accessor for flash[:notice] + # Convenience accessor for <tt>flash[:notice]</tt>. def notice self[:notice] end - # Convenience accessor for flash[:notice]= + # Convenience accessor for <tt>flash[:notice]=</tt>. def notice=(message) self[:notice] = message end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 2c98ca03a8..a3291bb7ad 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -47,12 +47,10 @@ module ActionDispatch when Proc strategy.call(request.raw_post) when :xml_simple, :xml_node - data = Hash.from_xml(request.raw_post) || {} + data = request.deep_munge(Hash.from_xml(request.body.read) || {}) data.with_indifferent_access - when :yaml - YAML.load(request.raw_post) when :json - data = ActiveSupport::JSON.decode(request.raw_post) + data = request.deep_munge ActiveSupport::JSON.decode(request.body) data = {:_json => data} unless data.is_a?(Hash) data.with_indifferent_access else diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 6fb16bdfe9..7c12590c49 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -21,8 +21,6 @@ module ActionDispatch module Compatibility def initialize(app, options = {}) options[:key] ||= '_session_id' - # FIXME Rack's secret is not being used - options[:secret] ||= SecureRandom.hex(30) super end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 4437b50f1f..1e6ed624b0 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -36,21 +36,38 @@ module ActionDispatch # "rake secret" and set the key in config/initializers/secret_token.rb. # # Note that changing digest or secret invalidates all existing sessions! - class CookieStore < Rack::Session::Cookie + class CookieStore < Rack::Session::Abstract::ID include Compatibility include StaleSessionCheck include SessionObject - # Override rack's method + def initialize(app, options={}) + super(app, options.merge!(:cookie_only => true)) + end + def destroy_session(env, session_id, options) - new_sid = super + new_sid = generate_sid unless options[:drop] # Reset hash and Assign the new session id env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {} new_sid end + def load_session(env) + stale_session_check! do + data = unpacked_cookie_data(env) + data = persistent_session_id!(data) + [data["session_id"], data] + end + end + private + def extract_session_id(env) + stale_session_check! do + unpacked_cookie_data(env)["session_id"] + end + end + def unpacked_cookie_data(env) env["action_dispatch.request.unsigned_session_cookie"] ||= begin stale_session_check! do @@ -62,6 +79,12 @@ module ActionDispatch end end + def persistent_session_id!(data, sid=nil) + data ||= {} + data["session_id"] ||= sid || generate_sid + data + end + def set_session(env, sid, session_data, options) session_data["session_id"] = sid session_data diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 668515df59..08253de3f4 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -6,7 +6,7 @@ require 'action_view/log_subscriber' module ActionView #:nodoc: # = Action View Base # - # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERb + # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERB # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> extension then Jim Weirich's Builder::XmlMarkup library is used. # # == ERB diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 8a1e886b7f..50f1eff4ec 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -84,7 +84,7 @@ module ActionView # # <form action="/people/256" class="edit_person" id="edit_person_256" method="post"> # <div style="margin:0;padding:0;display:inline"> - # <input name="_method" type="hidden" value="put" /> + # <input name="_method" type="hidden" value="patch" /> # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> # </div> # <label for="person_first_name">First name</label>: @@ -242,7 +242,7 @@ module ActionView # # is then equivalent to something like: # - # <%= form_for @post, as: :post, url: post_path(@post), method: :put, html: { class: "edit_post", id: "edit_post_45" } do |f| %> + # <%= form_for @post, as: :post, url: post_path(@post), method: :patch, html: { class: "edit_post", id: "edit_post_45" } do |f| %> # ... # <% end %> # @@ -318,7 +318,7 @@ module ActionView # # <form action='http://www.example.com' method='post' data-remote='true'> # <div style='margin:0;padding:0;display:inline'> - # <input name='_method' type='hidden' value='put' /> + # <input name='_method' type='hidden' value='patch' /> # </div> # ... # </form> @@ -336,7 +336,7 @@ module ActionView # # <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'> # <div style='margin:0;padding:0;display:inline'> - # <input name='_method' type='hidden' value='put' /> + # <input name='_method' type='hidden' value='patch' /> # </div> # ... # </form> diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 5745286464..86d9f94067 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -26,7 +26,7 @@ module ActionView # ==== Options # * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data". # * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post". - # If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt> + # If "patch", "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt> # is added to simulate the verb over post. # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to # pass custom authenticity token string, or to not add authenticity_token field at all diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index aeee662071..bade121d44 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -170,7 +170,7 @@ module ActionView # You can also use custom data attributes using the <tt>:data</tt> option: # # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" } - # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?"">Visit Other Site</a> + # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a> def link_to(name = nil, options = nil, html_options = nil, &block) html_options, options = options, name if block_given? options ||= {} diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index c0b9833603..0480295056 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -116,22 +116,22 @@ class WebServiceTest < ActionDispatch::IntegrationTest end end - def test_register_and_use_yaml + def test_post_xml_using_a_disallowed_type_attribute + $stderr = StringIO.new with_test_route_set do - with_params_parsers Mime::YAML => Proc.new { |d| YAML.load(d) } do - post "/", {"entry" => "loaded from yaml"}.to_yaml, - {'CONTENT_TYPE' => 'application/x-yaml'} + post '/', '<foo type="symbol">value</foo>', 'CONTENT_TYPE' => 'application/xml' + assert_response 500 - assert_equal 'entry', @controller.response.body - assert @controller.params.has_key?(:entry) - assert_equal 'loaded from yaml', @controller.params["entry"] - end + post '/', '<foo type="yaml">value</foo>', 'CONTENT_TYPE' => 'application/xml' + assert_response 500 end + ensure + $stderr = STDERR end - def test_register_and_use_yaml_as_symbol + def test_register_and_use_yaml with_test_route_set do - with_params_parsers Mime::YAML => :yaml do + with_params_parsers Mime::YAML => Proc.new { |d| YAML.load(d) } do post "/", {"entry" => "loaded from yaml"}.to_yaml, {'CONTENT_TYPE' => 'application/x-yaml'} @@ -211,36 +211,6 @@ class WebServiceTest < ActionDispatch::IntegrationTest end end - def test_typecast_as_yaml - with_test_route_set do - with_params_parsers Mime::YAML => :yaml do - yaml = (<<-YAML).strip - --- - data: - a: 15 - b: false - c: true - d: 2005-03-17 - e: 2005-03-17T21:41:07Z - f: unparsed - g: - - 1 - - hello - - 1974-07-25 - YAML - post "/", yaml, {'CONTENT_TYPE' => 'application/x-yaml'} - params = @controller.params - assert_equal 15, params[:data][:a] - assert_equal false, params[:data][:b] - assert_equal true, params[:data][:c] - assert_equal Date.new(2005,3,17), params[:data][:d] - assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e] - assert_equal "unparsed", params[:data][:f] - assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g] - end - end - end - private def with_params_parsers(parsers = {}) old_session = @integration_session diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index c0c3147e37..2c4a6c2147 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -30,6 +30,21 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest ) end + test "nils are stripped from collections" do + assert_parses( + {"person" => nil}, + "{\"person\":[null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + assert_parses( + {"person" => ['foo']}, + "{\"person\":[\"foo\",null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + assert_parses( + {"person" => nil}, + "{\"person\":[null, null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + end + test "logs error if parsing unsuccessful" do with_test_routing do output = StringIO.new diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb index 3cb430d83d..f072a9f717 100644 --- a/actionpack/test/dispatch/request/query_string_parsing_test.rb +++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb @@ -84,8 +84,8 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest assert_parses({"action" => nil}, "action") assert_parses({"action" => {"foo" => nil}}, "action[foo]") assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar]") - assert_parses({"action" => {"foo" => { "bar" => [] }}}, "action[foo][bar][]") - assert_parses({"action" => {"foo" => []}}, "action[foo][]") + assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar][]") + assert_parses({"action" => {"foo" => nil }}, "action[foo][]") assert_parses({"action"=>{"foo"=>[{"bar"=>nil}]}}, "action[foo][][bar]") end diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb index cb68667002..f13b64a3c7 100644 --- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb @@ -30,6 +30,23 @@ class XmlParamsParsingTest < ActionDispatch::IntegrationTest assert_equal "<ok>bar</ok>", resp.body end + def assert_parses(expected, xml) + with_test_routing do + post "/parse", xml, default_headers + assert_response :ok + assert_equal(expected, TestController.last_request_parameters) + end + end + + test "nils are stripped from collections" do + assert_parses( + {"hash" => { "person" => nil} }, + "<hash><person type=\"array\"><person nil=\"true\"/></person></hash>") + assert_parses( + {"hash" => { "person" => ['foo']} }, + "<hash><person type=\"array\"><person>foo</person><person nil=\"true\"/></person>\n</hash>") + end + test "parses hash params" do with_test_routing do xml = "<person><name>David</name></person>" |