diff options
Diffstat (limited to 'actionpack')
27 files changed, 108 insertions, 68 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index b9b771b930..4f95a9bab9 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,22 @@ +* Catch invalid UTF-8 querystring values and respond with BadRequest + + Check querystring params for invalid UTF-8 characters, and raise an + ActionController::BadRequest error if present. Previously these strings + would typically trigger errors further down the stack. + + *Grey Baker* + +* Parse RSS/ATOM responses as XML, not HTML. + + *Alexander Kaupanin* + +* Show helpful message in `BadRequest` exceptions due to invalid path + parameter encodings. + + Fixes #21923. + + *Agis Anastasopoulos* + * Deprecate `config.static_cache_control` in favor of `config.public_file_server.headers` diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 13795f0dd8..287550db42 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -61,7 +61,7 @@ module AbstractController # impossible to skip a callback defined using an anonymous proc # using #skip_action_callback def skip_action_callback(*names) - ActiveSupport::Deprecation.warn('`skip_action_callback` is deprecated and will be removed in the next major version of Rails. Please use skip_before_action, skip_after_action or skip_around_action instead.') + ActiveSupport::Deprecation.warn('`skip_action_callback` is deprecated and will be removed in Rails 5.1. Please use skip_before_action, skip_after_action or skip_around_action instead.') skip_before_action(*names, raise: false) skip_after_action(*names, raise: false) skip_around_action(*names, raise: false) diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 94ec62ec6f..8e040bb465 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/array/extract_options' require 'action_dispatch/middleware/stack' -require 'active_support/deprecation' require 'action_dispatch/http/request' require 'action_dispatch/http/response' diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index 18e003741d..5260dc0336 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -5,12 +5,10 @@ module ActionController class BadRequest < ActionControllerError #:nodoc: attr_reader :original_exception - def initialize(type = nil, e = nil) - return super() unless type && e - - super("Invalid #{type} parameters: #{e.message}") + def initialize(msg = nil, e = nil) + super(msg) @original_exception = e - set_backtrace e.backtrace + set_backtrace e.backtrace if e end end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 58df5c539e..6e346fadfe 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -91,11 +91,11 @@ module ActionController #:nodoc: # and accept Rails' defaults, life will be much easier. # # If you need to use a MIME type which isn't supported by default, you can register your own handlers in - # config/initializers/mime_types.rb as follows. + # +config/initializers/mime_types.rb+ as follows. # # Mime::Type.register "image/jpg", :jpg # - # Respond to also allows you to specify a common block for different formats by using any: + # Respond to also allows you to specify a common block for different formats by using +any+: # # def index # @people = Person.all @@ -151,7 +151,7 @@ module ActionController #:nodoc: # format.html.none { render "trash" } # end # - # Variants also support common `any`/`all` block that formats have. + # Variants also support common +any+/+all+ block that formats have. # # It works for both inline: # @@ -174,7 +174,7 @@ module ActionController #:nodoc: # request.variant = [:tablet, :phone] # # which will work similarly to formats and MIME types negotiation. If there will be no - # :tablet variant declared, :phone variant will be picked: + # +:tablet+ variant declared, +:phone+ variant will be picked: # # respond_to do |format| # format.html.none diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 172fbdf954..cce6fe7787 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -1,4 +1,3 @@ -require 'active_support/deprecation' require 'active_support/core_ext/string/filters' module ActionController diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 380e9d29b4..2cada1f68a 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -522,7 +522,7 @@ module ActionController @request.delete_header 'HTTP_COOKIE' if @request.have_cookie_jar? - unless @response.committed? + unless @request.cookie_jar.committed? @request.cookie_jar.write(@response) self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) end diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 95094c25c0..b64f660ec5 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,7 +1,6 @@ require 'singleton' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/starts_ends_with' -require 'active_support/deprecation' module Mime class Mimes diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index bf20a33d36..35e3ac304f 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -65,7 +65,7 @@ module ActionDispatch path_parameters.each do |key, value| next unless value.respond_to?(:valid_encoding?) unless value.valid_encoding? - raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}" + raise ActionController::BadRequest, "Invalid parameter encoding: #{key} => #{value.inspect}" end end end @@ -338,10 +338,13 @@ module ActionDispatch # Override Rack's GET method to support indifferent access def GET fetch_header("action_dispatch.request.query_parameters") do |k| - set_header k, Request::Utils.normalize_encode_params(super || {}) + rack_query_params = super || {} + # Check for non UTF-8 parameter values, which would cause errors later + Request::Utils.check_param_encoding(rack_query_params) + set_header k, Request::Utils.normalize_encode_params(rack_query_params) end rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e - raise ActionController::BadRequest.new(:query, e) + raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}", e) end alias :query_parameters :GET @@ -357,7 +360,7 @@ module ActionDispatch self.request_parameters = Request::Utils.normalize_encode_params(super || {}) raise rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e - raise ActionController::BadRequest.new(:request, e) + raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}", e) end alias :request_parameters :POST diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index c54efb6541..f0127aa276 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -149,7 +149,6 @@ module ActionDispatch # :nodoc: self.body, self.status = body, status - @blank = false @cv = new_cond @committed = false @sending = false @@ -287,12 +286,8 @@ module ActionDispatch # :nodoc: @stream.write string end - EMPTY = " " - # Allows you to manually set or override the response body. def body=(body) - @blank = true if body == EMPTY - if body.respond_to?(:to_path) @stream = body else diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 6c7fba00cb..af9a29eb07 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -1,5 +1,3 @@ -require 'active_support/deprecation/reporting' - module ActionDispatch # ActionDispatch::Reloader provides prepare and cleanup callbacks, # intended to assist with code reloading during development. diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index a8151a8224..bb3df3c311 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -13,6 +13,21 @@ module ActionDispatch end end + def self.check_param_encoding(params) + case params + when Array + params.each { |element| check_param_encoding(element) } + when Hash + params.each_value { |value| check_param_encoding(value) } + when String + unless params.valid_encoding? + # Raise Rack::Utils::InvalidParameterError for consistency with Rack. + # ActionDispatch::Request#GET will re-raise as a BadRequest error. + raise Rack::Utils::InvalidParameterError, "Non UTF-8 value: #{params}" + end + end + end + class ParamEncoder # :nodoc: # Convert nested Hash to HashWithIndifferentAccess. # diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 921cda91ee..7c0404ca62 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -3,7 +3,6 @@ require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/enumerable' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/regexp' -require 'active_support/deprecation' require 'action_dispatch/routing/redirection' require 'action_dispatch/routing/endpoint' diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 339e2b7c4a..5f54ea130b 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,5 +1,4 @@ require 'action_dispatch/journey' -require 'forwardable' require 'active_support/concern' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index 8dd0bd63ad..fae266273e 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -12,7 +12,7 @@ module ActionDispatch include Rails::Dom::Testing::Assertions def html_document - @html_document ||= if @response.content_type === Mime[:xml] + @html_document ||= if @response.content_type.to_s =~ /xml\z/ Nokogiri::XML::Document.parse(@response.body) else Nokogiri::HTML::Document.parse(@response.body) diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 7e59bb68cf..790f9ea5d2 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -131,35 +131,35 @@ module ActionDispatch # Performs a GET request, following any subsequent redirect. # See +request_via_redirect+ for more information. def get_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:get, path, *args) end # Performs a POST request, following any subsequent redirect. # See +request_via_redirect+ for more information. def post_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:post, path, *args) end # Performs a PATCH request, following any subsequent redirect. # See +request_via_redirect+ for more information. def patch_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:patch, path, *args) end # Performs a PUT request, following any subsequent redirect. # See +request_via_redirect+ for more information. def put_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:put, path, *args) end # Performs a DELETE request, following any subsequent redirect. # See +request_via_redirect+ for more information. def delete_via_redirect(path, *args) - ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in Rails 5.1. Please use follow_redirect! manually after the request call for the same behavior.') request_via_redirect(:delete, path, *args) end end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index de7e800ac1..d0a1d1285f 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -402,6 +402,8 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest format.html { render plain: "OK", status: 200 } format.js { render plain: "JS OK", status: 200 } format.xml { render :xml => "<root></root>", :status => 200 } + format.rss { render :xml => "<root></root>", :status => 200 } + format.atom { render :xml => "<root></root>", :status => 200 } end end @@ -458,19 +460,21 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest end end - def test_get_xml - with_test_route_set do - get "/get", params: {}, headers: {"HTTP_ACCEPT" => "application/xml"} - assert_equal 200, status - assert_equal "OK", status_message - assert_response 200 - assert_response :success - assert_response :ok - assert_equal({}, cookies.to_hash) - assert_equal "<root></root>", body - assert_equal "<root></root>", response.body - assert_instance_of Nokogiri::XML::Document, html_document - assert_equal 1, request_count + def test_get_xml_rss_atom + %w[ application/xml application/rss+xml application/atom+xml ].each do |mime_string| + with_test_route_set do + get "/get", headers: {"HTTP_ACCEPT" => mime_string} + assert_equal 200, status + assert_equal "OK", status_message + assert_response 200 + assert_response :success + assert_response :ok + assert_equal({}, cookies.to_hash) + assert_equal "<root></root>", body + assert_equal "<root></root>", response.body + assert_instance_of Nokogiri::XML::Document, html_document + assert_equal 1, request_count + end end end diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 4d1c23cbee..4224ac2a1b 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -388,8 +388,14 @@ module ActionController end def test_exception_callback_when_committed + current_threads = Thread.list + capture_log_output do |output| get :exception_with_callback, format: 'text/event-stream' + + # Wait on the execution of all threads + (Thread.list - current_threads).each(&:join) + assert_equal %(data: "500 Internal Server Error"\n\n), response.body assert_match 'An exception occurred...', output.rewind && output.read assert_stream_closed diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb index 435bb18dce..048458178c 100644 --- a/actionpack/test/controller/new_base/render_text_test.rb +++ b/actionpack/test/controller/new_base/render_text_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'active_support/deprecation' module RenderText class MinimalController < ActionController::Metal diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb index 6c57c4caeb..744d8664be 100644 --- a/actionpack/test/controller/parameters/mutators_test.rb +++ b/actionpack/test/controller/parameters/mutators_test.rb @@ -62,15 +62,11 @@ class ParametersMutatorsTest < ActiveSupport::TestCase end test "select! retains permitted status" do - jruby_skip "https://github.com/jruby/jruby/issues/3137" - @params.permit! assert @params.select! { |k| k != "person" }.permitted? end test "select! retains unpermitted status" do - jruby_skip "https://github.com/jruby/jruby/issues/3137" - assert_not @params.select! { |k| k != "person" }.permitted? end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 82c7ebf568..256ebf6a07 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -629,13 +629,13 @@ class HttpCacheForeverTest < ActionController::TestCase def test_cache_with_public get :cache_me_forever, params: {public: true} - assert_equal "max-age=#{100.years.to_i}, public", @response.headers["Cache-Control"] + assert_equal "max-age=#{100.years}, public", @response.headers["Cache-Control"] assert_not_nil @response.etag end def test_cache_with_private get :cache_me_forever - assert_equal "max-age=#{100.years.to_i}, private", @response.headers["Cache-Control"] + assert_equal "max-age=#{100.years}, private", @response.headers["Cache-Control"] assert_not_nil @response.etag assert_response :success end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 40c97abd35..f1a296682d 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -139,7 +139,7 @@ XML def delete_cookie cookies.delete("foo") - head :ok + render plain: 'ok' end def test_without_body diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index af2ed24f43..e9896a71f4 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -961,15 +961,33 @@ class RequestParameters < BaseRequestTest end end + test "path parameters with invalid UTF8 encoding" do + request = stub_request( + "action_dispatch.request.path_parameters" => { foo: "\xBE" } + ) + + err = assert_raises(ActionController::BadRequest) do + request.check_path_parameters! + end + + assert_match "Invalid parameter encoding", err.message + assert_match "foo", err.message + assert_match "\\xBE", err.message + end + test "parameters not accessible after rack parse error of invalid UTF8 character" do request = stub_request("QUERY_STRING" => "foo%81E=1") + assert_raises(ActionController::BadRequest) { request.parameters } + end - 2.times do - assert_raises(ActionController::BadRequest) do - # rack will raise a Rack::Utils::InvalidParameterError when parsing this query string - request.parameters - end - end + test "parameters containing an invalid UTF8 character" do + request = stub_request("QUERY_STRING" => "foo=%81E") + assert_raises(ActionController::BadRequest) { request.parameters } + end + + test "parameters containing a deeply nested invalid UTF8 character" do + request = stub_request("QUERY_STRING" => "foo[bar]=%81E") + assert_raises(ActionController::BadRequest) { request.parameters } end test "parameters not accessible after rack parse error 1" do diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 126379a23c..981d820ccf 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -206,8 +206,6 @@ class ResponseTest < ActiveSupport::TestCase end test "read content type with charset utf-16" do - jruby_skip "https://github.com/jruby/jruby/issues/3138" - original = ActionDispatch::Response.default_charset begin ActionDispatch::Response.default_charset = 'utf-16' diff --git a/actionpack/test/dispatch/routing/concerns_test.rb b/actionpack/test/dispatch/routing/concerns_test.rb index 361ceca677..7ef513b0c8 100644 --- a/actionpack/test/dispatch/routing/concerns_test.rb +++ b/actionpack/test/dispatch/routing/concerns_test.rb @@ -109,8 +109,6 @@ class RoutingConcernsTest < ActionDispatch::IntegrationTest end def test_concerns_executes_block_in_context_of_current_mapper - jruby_skip "https://github.com/jruby/jruby/issues/3143" - mapper = ActionDispatch::Routing::Mapper.new(ActionDispatch::Routing::RouteSet.new) mapper.concern :test_concern do resources :things diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index cbb12a2209..15dd702161 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -93,13 +93,13 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest assert_kind_of AbstractController::ActionNotFound, env["action_dispatch.exception"] assert_equal "/404", env["PATH_INFO"] assert_equal "/not_found_original_exception", env["action_dispatch.original_path"] - [404, { "Content-Type" => "text/plain" }, ["YOU FAILED BRO"]] + [404, { "Content-Type" => "text/plain" }, ["YOU FAILED"]] end @app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app) get "/not_found_original_exception", headers: { 'action_dispatch.show_exceptions' => true } assert_response 404 - assert_equal "YOU FAILED BRO", body + assert_equal "YOU FAILED", body end test "returns an empty response if custom exceptions app returns X-Cascade pass" do diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index e62ed09b0a..1da57ab50b 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -75,8 +75,6 @@ module StaticTests end def test_served_static_file_with_non_english_filename - jruby_skip "Stop skipping if following bug gets fixed: " \ - "http://jira.codehaus.org/browse/JRUBY-7192" assert_html "means hello in Japanese\n", get("/foo/#{Rack::Utils.escape("こんにちは.html")}") end |