diff options
Diffstat (limited to 'actionpack')
102 files changed, 1111 insertions, 862 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index d0662bdae2..54937617df 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,408 +1,37 @@ -* Fix `NameError` raised in `ActionController::Renderer#with_defaults` +* AEAD encrypted cookies and sessions with GCM - *Hiroyuki Ishii* + Encrypted cookies now use AES-GCM which couples authentication and + encryption in one faster step and produces shorter ciphertexts. Cookies + encrypted using AES in CBC HMAC mode will be seamlessly upgraded when + this new mode is enabled via the + `action_dispatch.use_authenticated_cookie_encryption` configuration value. -* Added `#reverse_merge` and `#reverse_merge!` methods to `ActionController::Parameters` + *Michael J Coyne* - *Edouard Chin*, *Mitsutaka Mimura* +* Change the cache key format for fragments to make it easier to debug key churn. The new format is: -* Fix malformed URLS when using `ApplicationController.renderer` - - The Rack environment variable `rack.url_scheme` was not being set so `scheme` was - returning `nil`. This caused URLs to be malformed with the default settings. - Fix this by setting `rack.url_scheme` when the environment is normalized. - - Fixes #28151. - - *George Vrettos* - -* Commit flash changes when using a redirect route. - - Fixes #27992. - - *Andrew White* - - -## Rails 5.1.0.beta1 (February 23, 2017) ## - -* Prefer `remove_method` over `undef_method` when reloading routes - - When `undef_method` is used it prevents access to other implementations of that - url helper in the ancestor chain so use `remove_method` instead to restore access. - - *Andrew White* - -* Add the `resolve` method to the routing DSL - - This new method allows customization of the polymorphic mapping of models: - - ``` ruby - resource :basket - resolve("Basket") { [:basket] } - ``` - - ``` erb - <%= form_for @basket do |form| %> - <!-- basket form --> - <% end %> - ``` - - This generates the correct singular URL for the form instead of the default - resources member url, e.g. `/basket` vs. `/basket/:id`. - - Fixes #1769. - - *Andrew White* - -* Add the `direct` method to the routing DSL - - This new method allows creation of custom url helpers, e.g: - - ``` ruby - direct(:apple) { "http://www.apple.com" } - - >> apple_url - => "http://www.apple.com" - ``` - - This has the advantage of being available everywhere url helpers are available - unlike custom url helpers defined in helper modules, etc. - - *Andrew White* - -* Add `ActionDispatch::SystemTestCase` to Action Pack - - Adds Capybara integration directly into Rails through Action Pack! - - See PR [#26703](https://github.com/rails/rails/pull/26703) - - *Eileen M. Uchitelle* - -* Remove deprecated `.to_prepare`, `.to_cleanup`, `.prepare!` and `.cleanup!` from `ActionDispatch::Reloader`. - - *Rafael Mendonça França* - -* Remove deprecated `ActionDispatch::Callbacks.to_prepare` and `ActionDispatch::Callbacks.to_cleanup`. - - *Rafael Mendonça França* - -* Remove deprecated `ActionController::Metal.call`. - - *Rafael Mendonça França* - -* Remove deprecated `ActionController::Metal#env`. - - *Rafael Mendonça França* - -* Make `with_routing` test helper work when testing controllers inheriting from `ActionController::API` - - *Julia López* - -* Use accept header in integration tests with `as: :json` - - Instead of appending the `format` to the request path, Rails will figure - out the format from the header instead. - - This allows devs to use `:as` on routes that don't have a format. - - Fixes #27144. - - *Kasper Timm Hansen* - -* Reset a new session directly after its creation in `ActionDispatch::IntegrationTest#open_session`. - - Fixes #22742. - - *Tawan Sierek* - -* Fixes incorrect output from `rails routes` when using singular resources. - - Fixes #26606. - - *Erick Reyna* - -* Fixes multiple calls to `logger.fatal` instead of a single call, - for every line in an exception backtrace, when printing trace - from `DebugExceptions` middleware. - - Fixes #26134. - - *Vipul A M* - -* Add support for arbitrary hashes in strong parameters: - - ```ruby - params.permit(preferences: {}) - ``` - - *Xavier Noria* - -* Add `ActionController::Parameters#merge!`, which behaves the same as `Hash#merge!`. - - *Yuji Yaginuma* - -* Allow keys not found in `RACK_KEY_TRANSLATION` for setting the environment when rendering - arbitrary templates. - - *Sammy Larbi* - -* Remove deprecated support to non-keyword arguments in `ActionDispatch::IntegrationTest#process`, - `#get`, `#post`, `#patch`, `#put`, `#delete`, and `#head`. - - *Rafael Mendonça França* - -* Remove deprecated `ActionDispatch::IntegrationTest#*_via_redirect`. - - *Rafael Mendonça França* - -* Remove deprecated `ActionDispatch::IntegrationTest#xml_http_request`. - - *Rafael Mendonça França* - -* Remove deprecated support for passing `:path` and route path as strings in `ActionDispatch::Routing::Mapper#match`. - - *Rafael Mendonça França* - -* Remove deprecated support for passing path as `nil` in `ActionDispatch::Routing::Mapper#match`. - - *Rafael Mendonça França* - -* Remove deprecated `cache_control` argument from `ActionDispatch::Static#initialize`. - - *Rafael Mendonça França* - -* Remove deprecated support to passing strings or symbols to the middleware stack. - - *Rafael Mendonça França* - -* Change HSTS subdomain to true. - - *Rafael Mendonça França* - -* Remove deprecated `host` and `port` ssl options. - - *Rafael Mendonça França* - -* Remove deprecated `const_error` argument in - `ActionDispatch::Session::SessionRestoreError#initialize`. - - *Rafael Mendonça França* - -* Remove deprecated `#original_exception` in `ActionDispatch::Session::SessionRestoreError`. - - *Rafael Mendonça França* - -* Deprecate `ActionDispatch::ParamsParser::ParseError` in favor of - `ActionDispatch::Http::Parameters::ParseError`. - - *Rafael Mendonça França* - -* Remove deprecated `ActionDispatch::ParamsParser`. - - *Rafael Mendonça França* - -* Remove deprecated `original_exception` and `message` arguments in - `ActionDispatch::ParamsParser::ParseError#initialize`. - - *Rafael Mendonça França* - -* Remove deprecated `#original_exception` in `ActionDispatch::ParamsParser::ParseError`. - - *Rafael Mendonça França* - -* Remove deprecated access to mime types through constants. - - *Rafael Mendonça França* - -* Remove deprecated support to non-keyword arguments in `ActionController::TestCase#process`, - `#get`, `#post`, `#patch`, `#put`, `#delete`, and `#head`. - - *Rafael Mendonça França* - -* Remove deprecated `xml_http_request` and `xhr` methods in `ActionController::TestCase`. - - *Rafael Mendonça França* - -* Remove deprecated methods in `ActionController::Parameters`. - - *Rafael Mendonça França* - -* Remove deprecated support to comparing a `ActionController::Parameters` - with a `Hash`. - - *Rafael Mendonça França* - -* Remove deprecated support to `:text` in `render`. - - *Rafael Mendonça França* - -* Remove deprecated support to `:nothing` in `render`. - - *Rafael Mendonça França* - -* Remove deprecated support to `:back` in `redirect_to`. - - *Rafael Mendonça França* - -* Remove deprecated support to passing status as option `head`. - - *Rafael Mendonça França* - -* Remove deprecated support to passing original exception to `ActionController::BadRequest` - and the `ActionController::BadRequest#original_exception` method. - - *Rafael Mendonça França* - -* Remove deprecated methods `skip_action_callback`, `skip_filter`, `before_filter`, - `prepend_before_filter`, `skip_before_filter`, `append_before_filter`, `around_filter` - `prepend_around_filter`, `skip_around_filter`, `append_around_filter`, `after_filter`, - `prepend_after_filter`, `skip_after_filter` and `append_after_filter`. - - *Rafael Mendonça França* - -* Show an "unmatched constraints" error when params fail to match constraints - on a matched route, rather than a "missing keys" error. - - Fixes #26470. - - *Chris Carter* - -* Fix adding implicitly rendered template digests to ETags. - - Fixes a case when modifying an implicitly rendered template for a - controller action using `fresh_when` or `stale?` would not result in a new - `ETag` value. - - *Javan Makhmali* - -* Make `fixture_file_upload` work in integration tests. - - *Yuji Yaginuma* - -* Add `to_param` to `ActionController::Parameters` deprecations. - - In the future `ActionController::Parameters` are discouraged from being used - in URLs without explicit whitelisting. Go through `to_h` to use `to_param`. - - *Kir Shatrov* - -* Fix nested multiple roots - - The PR #20940 enabled the use of multiple roots with different constraints - at the top level but unfortunately didn't work when those roots were inside - a namespace and also broke the use of root inside a namespace after a top - level root was defined because the check for the existence of the named route - used the global :root name and not the namespaced name. - - This is fixed by using the name_for_action method to expand the :root name to - the full namespaced name. We can pass nil for the second argument as we're not - dealing with resource definitions so don't need to handle the cases for edit - and new routes. - - Fixes #26148. - - *Ryo Hashimoto*, *Andrew White* - -* Include the content of the flash in the auto-generated etag. This solves the following problem: - - 1. POST /messages - 2. redirect_to messages_url, notice: 'Message was created' - 3. GET /messages/1 - 4. GET /messages - - Step 4 would before still include the flash message, even though it's no longer relevant, - because the etag cache was recorded with the flash in place and didn't change when it was gone. + views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123 + ^template path ^template tree digest ^class ^id *DHH* -* SSL: Changes redirect behavior for all non-GET and non-HEAD requests - (like POST/PUT/PATCH etc) to `http://` resources to redirect to `https://` - with a [307 status code](http://tools.ietf.org/html/rfc7231#section-6.4.7) instead of [301 status code](http://tools.ietf.org/html/rfc7231#section-6.4.2). - - 307 status code instructs the HTTP clients to preserve the original - request method while redirecting. It has been part of HTTP RFC since - 1999 and is implemented/recognized by most (if not all) user agents. - - # Before - POST http://example.com/articles (i.e. ArticlesContoller#create) - redirects to - GET https://example.com/articles (i.e. ArticlesContoller#index) - - # After - POST http://example.com/articles (i.e. ArticlesContoller#create) - redirects to - POST https://example.com/articles (i.e. ArticlesContoller#create) - - *Chirag Singhal* +* Add support for recyclable cache keys with fragment caching. This uses the new versioned entries in the + `ActiveSupport::Cache` stores and relies on the fact that Active Record has split `#cache_key` and `#cache_version` + to support it. -* Add `:as` option to `ActionController:TestCase#process` and related methods. - - Specifying `as: mime_type` allows the `CONTENT_TYPE` header to be specified - in controller tests without manually doing this through `@request.headers['CONTENT_TYPE']`. - - *Everest Stefan Munro-Zeisberger* - -* Show cache hits and misses when rendering partials. - - Partials using the `cache` helper will show whether a render hit or missed - the cache: - - ``` - Rendered messages/_message.html.erb in 1.2 ms [cache hit] - Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss] - ``` - - This removes the need for the old fragment cache logging: - - ``` - Read fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/d0bdf2974e1ef6d31685c3b392ad0b74 (0.6ms) - Rendered messages/_message.html.erb in 1.2 ms [cache hit] - Write fragment views/v1/2914079/v1/2914079/recordings/70182313-20160225015037000000/3b4e249ac9d168c617e32e84b99218b5 (1.1ms) - Rendered recordings/threads/_thread.html.erb in 1.5 ms [cache miss] - ``` - - Though that full output can be reenabled with - `config.action_controller.enable_fragment_cache_logging = true`. - - *Stan Lo* - -* Don't override the `Accept` header in integration tests when called with `xhr: true`. - - Fixes #25859. - - *David Chen* - -* Fix `defaults` option for root route. - - A regression from some refactoring for the 5.0 release, this change - fixes the use of `defaults` (default parameters) in the `root` routing method. - - *Chris Arcand* - -* Check `request.path_parameters` encoding at the point they're set. - - Check for any non-UTF8 characters in path parameters at the point they're - set in `env`. Previously they were checked for when used to get a controller - class, but this meant routes that went directly to a Rack app, or skipped - controller instantiation for some other reason, had to defend against - non-UTF8 characters themselves. - - *Grey Baker* + *DHH* -* Don't raise `ActionController::UnknownHttpMethod` from `ActionDispatch::Static`. +* Add `action_controller_api` and `action_controller_base` load hooks to be called in `ActiveSupport.on_load` - Pass `Rack::Request` objects to `ActionDispatch::FileHandler` to avoid it - raising `ActionController::UnknownHttpMethod`. If an unknown method is - passed, it should pass exception higher in the stack instead, once we've had a - chance to define exception handling behaviour. + `ActionController::Base` and `ActionController::API` have differing implementations. This means that + the one umbrella hook `action_controller` is not able to address certain situations where a method + may not exist in a certain implementation. - *Grey Baker* + This is fixed by adding two new hooks so you can target `ActionController::Base` vs `ActionController::API` -* Handle `Rack::QueryParser` errors in `ActionDispatch::ExceptionWrapper`. + Fixes #27013. - Updated `ActionDispatch::ExceptionWrapper` to handle the Rack 2.0 namespace - for `ParameterTypeError` and `InvalidParameterError` errors. + *Julian Nadeau* - *Grey Baker* -Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actionpack/CHANGELOG.md) for previous changes. +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionpack/CHANGELOG.md) for previous changes. diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 31dd1865f9..69408c8aab 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -26,7 +26,7 @@ namespace :test do end task :lines do - load File.expand_path("..", File.dirname(__FILE__)) + "/tools/line_statistics" + load File.expand_path("..", __dir__) + "/tools/line_statistics" files = FileList["lib/**/*.rb"] CodeTools::LineStatistics.new(files).print_loc end diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 2c24a54305..31803042dd 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -1,4 +1,4 @@ -version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip +version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index e7cb6347a2..dc79820a82 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -14,8 +14,16 @@ module AbstractController # expected to provide their own +render+ method, since rendering means # different things depending on the context. class Base + ## + # Returns the body of the HTTP response sent by the controller. attr_internal :response_body + + ## + # Returns the name of the action this controller is processing. attr_internal :action_name + + ## + # Returns the formats that can be processed by the controller. attr_internal :formats include ActiveSupport::Configurable diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb index c85b4adba1..14e4a82523 100644 --- a/actionpack/lib/abstract_controller/caching/fragments.rb +++ b/actionpack/lib/abstract_controller/caching/fragments.rb @@ -25,7 +25,10 @@ module AbstractController self.fragment_cache_keys = [] - helper_method :fragment_cache_key if respond_to?(:helper_method) + if respond_to?(:helper_method) + helper_method :fragment_cache_key + helper_method :combined_fragment_cache_key + end end module ClassMethods @@ -62,17 +65,36 @@ module AbstractController # with the specified +key+ value. The key is expanded using # ActiveSupport::Cache.expand_cache_key. def fragment_cache_key(key) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Calling fragment_cache_key directly is deprecated and will be removed in Rails 6.0. + All fragment accessors now use the combined_fragment_cache_key method that retains the key as an array, + such that the caching stores can interrogate the parts for cache versions used in + recyclable cache keys. + MSG + head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) } tail = key.is_a?(Hash) ? url_for(key).split("://").last : key ActiveSupport::Cache.expand_cache_key([*head, *tail], :views) end + # Given a key (as described in +expire_fragment+), returns + # a key array suitable for use in reading, writing, or expiring a + # cached fragment. All keys begin with <tt>:views</tt>, + # followed by ENV["RAILS_CACHE_ID"] or ENV["RAILS_APP_VERSION"] if set, + # followed by any controller-wide key prefix values, ending + # with the specified +key+ value. + def combined_fragment_cache_key(key) + head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) } + tail = key.is_a?(Hash) ? url_for(key).split("://").last : key + [ :views, (ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]), *head, *tail ].compact + end + # Writes +content+ to the location signified by # +key+ (see +expire_fragment+ for acceptable formats). def write_fragment(key, content, options = nil) return content unless cache_configured? - key = fragment_cache_key(key) + key = combined_fragment_cache_key(key) instrument_fragment_cache :write_fragment, key do content = content.to_str cache_store.write(key, content, options) @@ -85,7 +107,7 @@ module AbstractController def read_fragment(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) + key = combined_fragment_cache_key(key) instrument_fragment_cache :read_fragment, key do result = cache_store.read(key, options) result.respond_to?(:html_safe) ? result.html_safe : result @@ -96,7 +118,7 @@ module AbstractController # +key+ exists (see +expire_fragment+ for acceptable formats). def fragment_exist?(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) + key = combined_fragment_cache_key(key) instrument_fragment_cache :exist_fragment?, key do cache_store.exist?(key, options) @@ -123,7 +145,7 @@ module AbstractController # method (or <tt>delete_matched</tt>, for Regexp keys). def expire_fragment(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) unless key.is_a?(Regexp) + key = combined_fragment_cache_key(key) unless key.is_a?(Regexp) instrument_fragment_cache :expire_fragment, key do if key.is_a?(Regexp) @@ -135,8 +157,7 @@ module AbstractController end def instrument_fragment_cache(name, key) # :nodoc: - payload = instrument_payload(key) - ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", payload) { yield } + ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", instrument_payload(key)) { yield } end end end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index ce4ecf17cc..ba7dec6083 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -1,4 +1,24 @@ module AbstractController + # = Abstract Controller Callbacks + # + # Abstract Controller provides hooks during the life cycle of a controller action. + # Callbacks allow you to trigger logic during this cycle. Available callbacks are: + # + # * <tt>after_action</tt> + # * <tt>append_after_action</tt> + # * <tt>append_around_action</tt> + # * <tt>append_before_action</tt> + # * <tt>around_action</tt> + # * <tt>before_action</tt> + # * <tt>prepend_after_action</tt> + # * <tt>prepend_around_action</tt> + # * <tt>prepend_before_action</tt> + # * <tt>skip_after_action</tt> + # * <tt>skip_around_action</tt> + # * <tt>skip_before_action</tt> + # + # NOTE: Calling the same callback multiple times will overwrite previous callback definitions. + # module Callbacks extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index 0d1af0d0bd..94698df730 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -141,6 +141,7 @@ module ActionController include mod end + ActiveSupport.run_load_hooks(:action_controller_api, self) ActiveSupport.run_load_hooks(:action_controller, self) end end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 0fe0853da3..8c2b111f89 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -261,6 +261,13 @@ module ActionController PROTECTED_IVARS end + def self.make_response!(request) + ActionDispatch::Response.create.tap do |res| + res.request = request + end + end + + ActiveSupport.run_load_hooks(:action_controller_base, self) ActiveSupport.run_load_hooks(:action_controller, self) end end diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index d29a5fe68f..d00fcbcd13 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -60,9 +60,9 @@ module ActionController class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{method}(event) return unless logger.info? && ActionController::Base.enable_fragment_cache_logging - key_or_path = event.payload[:key] || event.payload[:path] + key = ActiveSupport::Cache.expand_cache_key(event.payload[:key] || event.payload[:path]) human_name = #{method.to_s.humanize.inspect} - info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)") + info("\#{human_name} \#{key} (\#{event.duration.round(1)}ms)") end METHOD end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 74c4153cd2..246644dcbd 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -129,7 +129,7 @@ module ActionController end def self.make_response!(request) - ActionDispatch::Response.create.tap do |res| + ActionDispatch::Response.new.tap do |res| res.request = request end end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 7b4c7b923e..96bd548268 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -182,7 +182,7 @@ module ActionController #:nodoc: # request.variant = [:tablet, :phone] # # This will work similarly to formats and MIME types negotiation. If there - # is no +:tablet+ variant declared, +:phone+ variant will be picked: + # is no +:tablet+ variant declared, the +:phone+ variant will be used: # # respond_to do |format| # format.html.none diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 3cca5e8906..a89fc1678b 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -105,7 +105,11 @@ module ActionController unless super || exclude if m.respond_to?(:attribute_names) && m.attribute_names.any? - self.include = m.attribute_names + if m.respond_to?(:stored_attributes) && !m.stored_attributes.empty? + self.include = m.attribute_names + m.stored_attributes.values.flatten.map(&:to_s) + else + self.include = m.attribute_names + end end end end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index d9a8b9c12d..5051c02a62 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -213,7 +213,11 @@ module ActionController #:nodoc: if !verified_request? if logger && log_warning_on_csrf_failure - logger.warn "Can't verify CSRF token authenticity." + if valid_request_origin? + logger.warn "Can't verify CSRF token authenticity." + else + logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})" + end end handle_unverified_request end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 1190e0ed69..20330b5091 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -43,6 +43,18 @@ module ActionController end end + # Raised when a Parameters instance is not marked as permitted and + # an operation to transform it to hash is called. + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.to_h + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + class UnfilteredParameters < ArgumentError + def initialize # :nodoc: + super("unable to convert unpermitted parameters to hash") + end + end + # == Action Controller \Parameters # # Allows you to choose which attributes should be whitelisted for mass updating @@ -53,9 +65,9 @@ module ActionController # # params = ActionController::Parameters.new({ # person: { - # name: 'Francesco', + # name: "Francesco", # age: 22, - # role: 'admin' + # role: "admin" # } # }) # @@ -103,7 +115,7 @@ module ActionController # You can fetch values of <tt>ActionController::Parameters</tt> using either # <tt>:key</tt> or <tt>"key"</tt>. # - # params = ActionController::Parameters.new(key: 'value') + # params = ActionController::Parameters.new(key: "value") # params[:key] # => "value" # params["key"] # => "value" class Parameters @@ -203,13 +215,13 @@ module ActionController # class Person < ActiveRecord::Base # end # - # params = ActionController::Parameters.new(name: 'Francesco') + # params = ActionController::Parameters.new(name: "Francesco") # params.permitted? # => false # Person.new(params) # => ActiveModel::ForbiddenAttributesError # # ActionController::Parameters.permit_all_parameters = true # - # params = ActionController::Parameters.new(name: 'Francesco') + # params = ActionController::Parameters.new(name: "Francesco") # params.permitted? # => true # Person.new(params) # => #<Person id: nil, name: "Francesco"> def initialize(parameters = {}) @@ -228,13 +240,14 @@ module ActionController end # Returns a safe <tt>ActiveSupport::HashWithIndifferentAccess</tt> - # representation of this parameter with all unpermitted keys removed. + # representation of the parameters with all unpermitted keys removed. # # params = ActionController::Parameters.new({ - # name: 'Senjougahara Hitagi', - # oddity: 'Heavy stone crab' + # name: "Senjougahara Hitagi", + # oddity: "Heavy stone crab" # }) - # params.to_h # => {} + # params.to_h + # # => ActionController::UnfilteredParameters: unable to convert unfiltered parameters to hash # # safe_params = params.permit(:name) # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} @@ -242,17 +255,61 @@ module ActionController if permitted? convert_parameters_to_hashes(@parameters, :to_h) else - slice(*self.class.always_permitted_parameters).permit!.to_h + raise UnfilteredParameters end end + # Returns a safe <tt>Hash</tt> representation of the parameters + # with all unpermitted keys removed. + # + # params = ActionController::Parameters.new({ + # name: "Senjougahara Hitagi", + # oddity: "Heavy stone crab" + # }) + # params.to_hash + # # => ActionController::UnfilteredParameters: unable to convert unfiltered parameters to hash + # + # safe_params = params.permit(:name) + # safe_params.to_hash # => {"name"=>"Senjougahara Hitagi"} + def to_hash + to_h.to_hash + end + + # Returns a string representation of the receiver suitable for use as a URL + # query string: + # + # params = ActionController::Parameters.new({ + # name: "David", + # nationality: "Danish" + # }) + # params.to_query + # # => "name=David&nationality=Danish" + # + # An optional namespace can be passed to enclose key names: + # + # params = ActionController::Parameters.new({ + # name: "David", + # nationality: "Danish" + # }) + # params.to_query("user") + # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" + # + # The string pairs "key=value" that conform the query string + # are sorted lexicographically in ascending order. + # + # This method is also aliased as +to_param+. + def to_query(*args) + to_h.to_query(*args) + end + alias_method :to_param, :to_query + # Returns an unsafe, unfiltered - # <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of this - # parameter. + # <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of the + # parameters. # # params = ActionController::Parameters.new({ - # name: 'Senjougahara Hitagi', - # oddity: 'Heavy stone crab' + # name: "Senjougahara Hitagi", + # oddity: "Heavy stone crab" # }) # params.to_unsafe_h # # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"} @@ -297,7 +354,7 @@ module ActionController # class Person < ActiveRecord::Base # end # - # params = ActionController::Parameters.new(name: 'Francesco') + # params = ActionController::Parameters.new(name: "Francesco") # params.permitted? # => false # Person.new(params) # => ActiveModel::ForbiddenAttributesError # params.permit! @@ -319,7 +376,7 @@ module ActionController # When passed a single key, if it exists and its associated value is # either present or the singleton +false+, returns said value: # - # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person) + # ActionController::Parameters.new(person: { name: "Francesco" }).require(:person) # # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false> # # Otherwise raises <tt>ActionController::ParameterMissing</tt>: @@ -352,7 +409,7 @@ module ActionController # Technically this method can be used to fetch terminal values: # # # CAREFUL - # params = ActionController::Parameters.new(person: { name: 'Finn' }) + # params = ActionController::Parameters.new(person: { name: "Finn" }) # name = params.require(:person).require(:name) # CAREFUL # # but take into account that at some point those ones have to be permitted: @@ -382,7 +439,7 @@ module ActionController # for the object to +true+. This is useful for limiting which attributes # should be allowed for mass updating. # - # params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' }) + # params = ActionController::Parameters.new(user: { name: "Francesco", age: 22, role: "admin" }) # permitted = params.require(:user).permit(:name, :age) # permitted.permitted? # => true # permitted.has_key?(:name) # => true @@ -402,7 +459,7 @@ module ActionController # You may declare that the parameter should be an array of permitted scalars # by mapping it to an empty array: # - # params = ActionController::Parameters.new(tags: ['rails', 'parameters']) + # params = ActionController::Parameters.new(tags: ["rails", "parameters"]) # params.permit(tags: []) # # Sometimes it is not possible or convenient to declare the valid keys of @@ -418,11 +475,11 @@ module ActionController # # params = ActionController::Parameters.new({ # person: { - # name: 'Francesco', + # name: "Francesco", # age: 22, # pets: [{ - # name: 'Purplish', - # category: 'dogs' + # name: "Purplish", + # category: "dogs" # }] # } # }) @@ -441,8 +498,8 @@ module ActionController # params = ActionController::Parameters.new({ # person: { # contact: { - # email: 'none@test.com', - # phone: '555-1234' + # email: "none@test.com", + # phone: "555-1234" # } # } # }) @@ -475,7 +532,7 @@ module ActionController # Returns a parameter for the given +key+. If not found, # returns +nil+. # - # params = ActionController::Parameters.new(person: { name: 'Francesco' }) + # params = ActionController::Parameters.new(person: { name: "Francesco" }) # params[:person] # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false> # params[:none] # => nil def [](key) @@ -494,11 +551,11 @@ module ActionController # if more arguments are given, then that will be returned; if a block # is given, then that will be run and its result returned. # - # params = ActionController::Parameters.new(person: { name: 'Francesco' }) + # params = ActionController::Parameters.new(person: { name: "Francesco" }) # params.fetch(:person) # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false> # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none - # params.fetch(:none, 'Francesco') # => "Francesco" - # params.fetch(:none) { 'Francesco' } # => "Francesco" + # params.fetch(:none, "Francesco") # => "Francesco" + # params.fetch(:none) { "Francesco" } # => "Francesco" def fetch(key, *args) convert_value_to_parameters( @parameters.fetch(key) { @@ -609,8 +666,8 @@ module ActionController # to key. If the key is not found, returns the default value. If the # optional code block is given and the key is not found, pass in the key # and return the result of block. - def delete(key) - convert_value_to_parameters(@parameters.delete(key)) + def delete(key, &block) + convert_value_to_parameters(@parameters.delete(key, &block)) end # Returns a new instance of <tt>ActionController::Parameters</tt> with only @@ -667,6 +724,7 @@ module ActionController other_hash.to_h.merge(@parameters) ) end + alias_method :with_defaults, :reverse_merge # Returns current <tt>ActionController::Parameters</tt> instance with # current hash merged into +other_hash+. @@ -674,6 +732,7 @@ module ActionController @parameters.merge!(other_hash.to_h) { |key, left, right| left } self end + alias_method :with_defaults!, :reverse_merge! # This is required by ActiveModel attribute assignment, so that user can # pass +Parameters+ to a mass assignment methods in a model. It should not @@ -713,8 +772,6 @@ module ActionController end end - undef_method :to_param - # Returns duplicate of object including all parameters. def deep_dup self.class.new(@parameters.deep_dup).tap do |duplicate| diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 72e29c2c9d..bc42d50205 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -534,7 +534,6 @@ module ActionController @request.delete_header "HTTP_ACCEPT" end @request.query_string = "" - @request.env.delete "PATH_INFO" @response.sent! end diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index c4fe3a5c09..19f89edbc1 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -135,9 +135,7 @@ module ActionDispatch } end - # Receives an array of mimes and return the first user sent mime that - # matches the order array. - # + # Returns the first MIME type that matches the provided array of MIME types. def negotiate_mime(order) formats.each do |priority| if priority == Mime::ALL diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 1583a8f87f..74a3afab44 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -46,7 +46,7 @@ module Mime end end - # Encapsulates the notion of a mime type. Can be used at render time, for example, with: + # Encapsulates the notion of a MIME type. Can be used at render time, for example, with: # # class PostsController < ActionController::Base # def show @@ -64,7 +64,7 @@ module Mime @register_callbacks = [] - # A simple helper class used in parsing the accept header + # A simple helper class used in parsing the accept header. class AcceptItem #:nodoc: attr_accessor :index, :name, :q alias :to_s :name @@ -72,7 +72,7 @@ module Mime def initialize(index, name, q = nil) @index = index @name = name - q ||= 0.0 if @name == "*/*".freeze # default wildcard match to end of list + q ||= 0.0 if @name == "*/*".freeze # Default wildcard match to end of list. @q = ((q || 1.0).to_f * 100).to_i end @@ -90,22 +90,22 @@ module Mime text_xml_idx = find_item_by_name list, "text/xml" app_xml_idx = find_item_by_name list, Mime[:xml].to_s - # Take care of the broken text/xml entry by renaming or deleting it + # Take care of the broken text/xml entry by renaming or deleting it. if text_xml_idx && app_xml_idx app_xml = list[app_xml_idx] text_xml = list[text_xml_idx] - app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two - if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list + app_xml.q = [text_xml.q, app_xml.q].max # Set the q value to the max of the two. + if app_xml_idx > text_xml_idx # Make sure app_xml is ahead of text_xml in the list. list[app_xml_idx], list[text_xml_idx] = text_xml, app_xml app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx end - list.delete_at(text_xml_idx) # delete text_xml from the list + list.delete_at(text_xml_idx) # Delete text_xml from the list. elsif text_xml_idx list[text_xml_idx].name = Mime[:xml].to_s end - # Look for more specific XML-based types and sort them ahead of app/xml + # Look for more specific XML-based types and sort them ahead of app/xml. if app_xml_idx app_xml = list[app_xml_idx] idx = app_xml_idx @@ -147,7 +147,7 @@ module Mime EXTENSION_LOOKUP[extension.to_s] end - # Registers an alias that's not used on mime type lookup, but can be referenced directly. Especially useful for + # Registers an alias that's not used on MIME type lookup, but can be referenced directly. Especially useful for # rendering different HTML versions depending on the user agent, like an iPhone. def register_alias(string, symbol, extension_synonyms = []) register(string, symbol, [], extension_synonyms, true) @@ -326,11 +326,11 @@ module Mime def ref; end - def respond_to_missing?(method, include_private = false) - method.to_s.ends_with? "?" - end - private + def respond_to_missing?(method, _) + method.to_s.ends_with? "?" + end + def method_missing(method, *args) false if method.to_s.ends_with? "?" end diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 8f21eca440..7c585dbe68 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -13,7 +13,7 @@ module ActionDispatch } # Raised when raw data from the request cannot be parsed by the parser - # defined for request's content mime type. + # defined for request's content MIME type. class ParseError < StandardError def initialize super($!.message) @@ -30,9 +30,9 @@ module ActionDispatch end module ClassMethods - # Configure the parameter parser for a given mime type. + # Configure the parameter parser for a given MIME type. # - # It accepts a hash where the key is the symbol of the mime type + # It accepts a hash where the key is the symbol of the MIME type # and the value is a proc. # # original_parsers = ActionDispatch::Request.parameter_parsers @@ -85,7 +85,7 @@ module ActionDispatch def set_binary_encoding(params) action = params[:action] - if controller_class.binary_params_for?(action) + if binary_params_for?(action) ActionDispatch::Request::Utils.each_param_value(params) do |param| param.force_encoding ::Encoding::ASCII_8BIT end @@ -93,6 +93,12 @@ module ActionDispatch params end + def binary_params_for?(action) + controller_class.binary_params_for?(action) + rescue NameError + false + end + def parse_formatted_parameters(parsers) return yield if content_length.zero? || content_mime_type.nil? @@ -100,7 +106,7 @@ module ActionDispatch begin strategy.call(raw_post) - rescue # JSON or Ruby code block errors + rescue # JSON or Ruby code block errors. my_logger = logger || ActiveSupport::Logger.new($stderr) my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}" @@ -115,6 +121,7 @@ module ActionDispatch end module ParamsParser - ParseError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("ActionDispatch::ParamsParser::ParseError", "ActionDispatch::Http::Parameters::ParseError") + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + deprecate_constant "ParseError", "ActionDispatch::Http::Parameters::ParseError" end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 19fa42ce12..6d42404a98 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -114,7 +114,7 @@ module ActionDispatch HTTP_METHOD_LOOKUP = {} - # Populate the HTTP method lookup cache + # Populate the HTTP method lookup cache. HTTP_METHODS.each { |method| HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym } @@ -165,12 +165,12 @@ module ActionDispatch def show_exceptions? # :nodoc: # We're treating `nil` as "unset", and we want the default setting to be - # `true`. This logic should be extracted to `env_config` and calculated + # `true`. This logic should be extracted to `env_config` and calculated # once. !(get_header("action_dispatch.show_exceptions".freeze) == false) end - # Returns a symbol form of the #request_method + # Returns a symbol form of the #request_method. def request_method_symbol HTTP_METHOD_LOOKUP[request_method] end @@ -182,7 +182,7 @@ module ActionDispatch @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header("REQUEST_METHOD")) end - # Returns a symbol form of the #method + # Returns a symbol form of the #method. def method_symbol HTTP_METHOD_LOOKUP[method] end @@ -267,7 +267,7 @@ module ActionDispatch # (which sets the action_dispatch.request_id environment variable). # # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging. - # This relies on the rack variable set by the ActionDispatch::RequestId middleware. + # This relies on the Rack variable set by the ActionDispatch::RequestId middleware. def request_id get_header ACTION_DISPATCH_REQUEST_ID end @@ -339,7 +339,7 @@ module ActionDispatch Session::Options.set self, options end - # Override Rack's GET method to support indifferent access + # Override Rack's GET method to support indifferent access. def GET fetch_header("action_dispatch.request.query_parameters") do |k| rack_query_params = super || {} @@ -352,7 +352,7 @@ module ActionDispatch end alias :query_parameters :GET - # Override Rack's POST method to support indifferent access + # Override Rack's POST method to support indifferent access. def POST fetch_header("action_dispatch.request.request_parameters") do pr = parse_formatted_parameters(params_parsers) do |params| diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index dc159596c4..2ee52eeb48 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -85,7 +85,7 @@ module ActionDispatch # :nodoc: cattr_accessor(:default_headers) include Rack::Response::Helpers - # Aliasing these off because AD::Http::Cache::Response defines them + # Aliasing these off because AD::Http::Cache::Response defines them. alias :_cache_control :cache_control alias :_cache_control= :cache_control= @@ -142,7 +142,7 @@ module ActionDispatch # :nodoc: private def each_chunk(&block) - @buf.each(&block) # extract into own method + @buf.each(&block) end end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 61ba052e45..225272d66e 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -27,14 +27,18 @@ module ActionDispatch @tempfile = hash[:tempfile] raise(ArgumentError, ":tempfile is required") unless @tempfile - @original_filename = hash[:filename] - if @original_filename + if hash[:filename] + @original_filename = hash[:filename].dup + begin @original_filename.encode!(Encoding::UTF_8) rescue EncodingError @original_filename.force_encoding(Encoding::UTF_8) end + else + @original_filename = nil end + @content_type = hash[:type] @headers = hash[:head] end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index a6937d54ff..b6be48a48b 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -101,10 +101,8 @@ module ActionDispatch end def add_trailing_slash(path) - # includes querysting if path.include?("?") path.sub!(/\?/, '/\&') - # does not have a .format elsif !path.include?(".") path.sub!(/[^\/]\z|\A\z/, '\&/') end diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index f3b8e82d32..326f4e52f9 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -15,7 +15,7 @@ module ActionDispatch def generate(name, options, path_parameters, parameterize = nil) constraints = path_parameters.merge(options) - missing_keys = nil # need for variable scope + missing_keys = nil match_route(name, constraints) do |route| parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize) diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb index d692f6415c..62f052ced6 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb @@ -18,14 +18,6 @@ module ActionDispatch @tt = transition_table end - def simulate(string) - ms = memos(string) { return } - MatchData.new(ms) - end - - alias :=~ :simulate - alias :match :simulate - def memos(string) input = StringScanner.new(string) state = [0] diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index beb9f1ef3b..45aff287b1 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -82,7 +82,7 @@ module ActionDispatch end def visualizer(paths, title = "FSM") - viz_dir = File.join File.dirname(__FILE__), "..", "visualizer" + viz_dir = File.join __dir__, "..", "visualizer" fsm_js = File.read File.join(viz_dir, "fsm.js") fsm_css = File.read File.join(viz_dir, "fsm.css") erb = File.read File.join(viz_dir, "index.html.erb") @@ -109,7 +109,6 @@ module ActionDispatch svg = to_svg javascripts = [states, fsm_js] - # Annoying hack warnings fun_routes = fun_routes stylesheets = stylesheets svg = svg diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 927fd369c4..7bc15aa6b3 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -89,8 +89,15 @@ module ActionDispatch end end + # Needed for `rails routes`. Picks up succinctly defined requirements + # for a route, for example route + # + # get 'photo/:id', :controller => 'photos', :action => 'show', + # :id => /[A-Z]\d{5}/ + # + # will have {:controller=>"photos", :action=>"show", :id=>/[A-Z]\d{5}/} + # as requirements. def requirements - # needed for rails `rails routes` @defaults.merge(path.requirements).delete_if { |_, v| /.+?/ == v } diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index d641642338..6d400f3364 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -5,7 +5,7 @@ module ActionDispatch # Normalizes URI path. # # Strips off trailing slash and ensures there is a leading slash. - # Also converts downcase url encoded string to uppercase. + # Also converts downcase URL encoded string to uppercase. # # normalize_path("/foo") # => "/foo" # normalize_path("/foo/") # => "/foo" @@ -13,11 +13,13 @@ module ActionDispatch # normalize_path("") # => "/" # normalize_path("/%ab") # => "/%AB" def self.normalize_path(path) + encoding = path.encoding path = "/#{path}" path.squeeze!("/".freeze) path.sub!(%r{/+\Z}, "".freeze) path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } path = "/" if path == "".freeze + path.force_encoding(encoding) path end @@ -59,11 +61,11 @@ module ActionDispatch end private - def escape(component, pattern) # :doc: + def escape(component, pattern) component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) end - def percent_encode(unsafe) # :doc: + def percent_encode(unsafe) safe = EMPTY.dup unsafe.each_byte { |b| safe << DEC2HEX[b] } safe @@ -84,6 +86,10 @@ module ActionDispatch ENCODER.escape_fragment(fragment.to_s) end + # Replaces any escaped sequences with their unescaped representations. + # + # uri = "/topics?title=Ruby%20on%20Rails" + # unescape_uri(uri) #=> "/topics?title=Ruby on Rails" def self.unescape_uri(uri) ENCODER.unescape_uri(uri) end diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 1c50192867..335797f4b9 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -154,7 +154,7 @@ module ActionDispatch end end - # Loop through the requirements AST + # Loop through the requirements AST. class Each < FunctionalVisitor # :nodoc: def visit(node, block) block.call(node) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index c61cb3fd68..c0dda1bba5 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -43,6 +43,10 @@ module ActionDispatch get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT end + def authenticated_encrypted_cookie_salt + get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT + end + def secret_token get_header Cookies::SECRET_TOKEN end @@ -149,6 +153,7 @@ module ActionDispatch SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze + AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze SECRET_TOKEN = "action_dispatch.secret_token".freeze SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze @@ -160,7 +165,7 @@ module ActionDispatch # Raised when storing more than 4K of session data. CookieOverflow = Class.new StandardError - # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed + # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed. module ChainedCookieJars # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example: # @@ -207,6 +212,9 @@ module ActionDispatch # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set, # legacy cookies signed with the old key generator will be transparently upgraded. # + # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+ + # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded. + # # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+. # # Example: @@ -219,6 +227,8 @@ module ActionDispatch @encrypted ||= if upgrade_legacy_signed_cookies? UpgradeLegacyEncryptedCookieJar.new(self) + elsif upgrade_legacy_hmac_aes_cbc_cookies? + UpgradeLegacyHmacAesCbcCookieJar.new(self) else EncryptedCookieJar.new(self) end @@ -240,6 +250,13 @@ module ActionDispatch def upgrade_legacy_signed_cookies? request.secret_token.present? && request.secret_key_base.present? end + + def upgrade_legacy_hmac_aes_cbc_cookies? + request.secret_key_base.present? && + request.authenticated_encrypted_cookie_salt.present? && + request.encrypted_signed_cookie_salt.present? && + request.encrypted_cookie_salt.present? + end end # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream @@ -345,16 +362,16 @@ module ActionDispatch options[:path] ||= "/" if options[:domain] == :all || options[:domain] == "all" - # if there is a provided tld length then we use it otherwise default domain regexp + # If there is a provided tld length then we use it otherwise default domain regexp. domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP - # if host is not ip and matches domain regexp + # If host is not ip and matches domain regexp. # (ip confirms to domain regexp so we explicitly check for ip) options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp) ".#{$&}" end elsif options[:domain].is_a? Array - # if host matches one of the supplied domains without a dot in front of it + # If host matches one of the supplied domains without a dot in front of it. options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") } end end @@ -404,7 +421,7 @@ module ActionDispatch @delete_cookies[name.to_s] == options end - # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie + # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie. def clear(options = {}) @cookies.each_key { |k| delete(k, options) } end @@ -576,9 +593,11 @@ module ActionDispatch "Read the upgrade documentation to learn more about this new config option." end - secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len] - sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "") - @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) + cipher = "aes-256-gcm" + key_len = ActiveSupport::MessageEncryptor.key_len(cipher) + secret = key_generator.generate_key(request.authenticated_encrypted_cookie_salt || "")[0, key_len] + + @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: ActiveSupport::MessageEncryptor::NullSerializer) end private @@ -603,6 +622,32 @@ module ActionDispatch include VerifyAndUpgradeLegacySignedMessage end + # UpgradeLegacyHmacAesCbcCookieJar is used by ActionDispatch::Session::CookieStore + # to upgrade cookies encrypted with AES-256-CBC with HMAC to AES-256-GCM + class UpgradeLegacyHmacAesCbcCookieJar < EncryptedCookieJar + def initialize(parent_jar) + super + + secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len] + sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "") + + @legacy_encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer) + end + + def decrypt_and_verify_legacy_encrypted_message(name, signed_message) + deserialize(name, @legacy_encryptor.decrypt_and_verify(signed_message)).tap do |value| + self[name] = { value: value } + end + rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage + nil + end + + private + def parse(name, signed_message) + super || decrypt_and_verify_legacy_encrypted_message(name, signed_message) + end + end + def initialize(app) @app = app end diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 1c720c5a8e..336a775880 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -10,7 +10,7 @@ module ActionDispatch # This middleware is responsible for logging exceptions and # showing a debugging page in case the request is local. class DebugExceptions - RESCUES_TEMPLATE_PATH = File.expand_path("../templates", __FILE__) + RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__) class DebugView < ActionView::Base def debug_params(params) diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index cbe2f4be4d..6b29ce63ba 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -65,7 +65,7 @@ module ActionDispatch self.flash = flash_hash.dup end - if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) + if (!session.respond_to?(:loaded?) || session.loaded?) && # reset_session uses {}, which doesn't implement #loaded? session.key?("flash") && session["flash"].nil? session.delete("flash") end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 8bae5bfeff..53d5a4918c 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -157,13 +157,13 @@ module ActionDispatch def ips_from(header) # :doc: return [] unless header - # Split the comma-separated list into an array of strings + # Split the comma-separated list into an array of strings. ips = header.strip.split(/[,\s]+/) ips.select do |ip| begin - # Only return IPs that are valid according to the IPAddr#new method + # Only return IPs that are valid according to the IPAddr#new method. range = IPAddr.new(ip).to_range - # we want to make sure nobody is sneaking a netmask in + # We want to make sure nobody is sneaking a netmask in. range.begin == range.end rescue ArgumentError nil diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index d9f018c8ac..21ccf5a097 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -53,7 +53,7 @@ module ActionDispatch rescue ArgumentError => argument_error if argument_error.message =~ %r{undefined class/module ([\w:]*\w)} begin - # Note that the regexp does not allow $1 to end with a ':' + # Note that the regexp does not allow $1 to end with a ':'. $1.constantize rescue LoadError, NameError raise ActionDispatch::Session::SessionRestoreError diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 90f26a1c33..5a99714ec2 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -8,7 +8,7 @@ module ActionDispatch # The exceptions app should be passed as parameter on initialization # of ShowExceptions. Every time there is an exception, ShowExceptions will # store the exception in env["action_dispatch.exception"], rewrite the - # PATH_INFO to the exception status code and call the rack app. + # PATH_INFO to the exception status code and call the Rack app. # # If the application returns a "X-Cascade" pass response, this middleware # will send an empty response as result with the correct status code. diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb index 429ea7057c..2d21ae63f5 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -60,7 +60,7 @@ <%= link_to "Path", "#", 'data-route-helper' => '_path', title: "Returns a relative path (without the http or domain)" %> / <%= link_to "Url", "#", 'data-route-helper' => '_url', - title: "Returns an absolute url (with the http and domain)" %> + title: "Returns an absolute URL (with the http and domain)" %> </th> <th><%# HTTP Verb %> </th> @@ -93,7 +93,7 @@ } } - // get JSON from url and invoke callback with result + // get JSON from URL and invoke callback with result function getJSON(url, success) { var xhr = new XMLHttpRequest(); xhr.open('GET', url); diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 16a18a7f25..7662e164b8 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -16,6 +16,7 @@ module ActionDispatch config.action_dispatch.signed_cookie_salt = "signed cookie" config.action_dispatch.encrypted_cookie_salt = "encrypted cookie" config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie" + config.action_dispatch.use_authenticated_cookie_encryption = false config.action_dispatch.perform_deep_munge = true config.action_dispatch.default_headers = { @@ -36,6 +37,8 @@ module ActionDispatch ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses) ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates) + config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie" if config.action_dispatch.use_authenticated_cookie_encryption + config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil? ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index a2a80f39fc..3547a8604f 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -7,10 +7,10 @@ module ActionDispatch ENV_SESSION_KEY = Rack::RACK_SESSION # :nodoc: ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS # :nodoc: - # Singleton object used to determine if an optional param wasn't specified + # Singleton object used to determine if an optional param wasn't specified. Unspecified = Object.new - # Creates a session hash, merging the properties of the previous session if any + # Creates a session hash, merging the properties of the previous session if any. def self.create(store, req, default_options) session_was = find req session = Request::Session.new(store, req) @@ -63,7 +63,7 @@ module ActionDispatch @req = req @delegate = {} @loaded = false - @exists = nil # we haven't checked yet + @exists = nil # We haven't checked yet. end def id @@ -79,7 +79,7 @@ module ActionDispatch options = self.options || {} @by.send(:delete_session, @req, options.id(@req), options) - # Load the new sid to be written with the response + # Load the new sid to be written with the response. @loaded = false load_for_write! end @@ -101,11 +101,13 @@ module ActionDispatch # Returns keys of the session as Array. def keys + load_for_read! @delegate.keys end # Returns values of the session as Array. def values + load_for_read! @delegate.values end diff --git a/actionpack/lib/action_dispatch/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index 01bc871e5f..3615e4b1d8 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -40,7 +40,6 @@ module ActionDispatch class ParamEncoder # :nodoc: # Convert nested Hash to HashWithIndifferentAccess. - # def self.normalize_encode_params(params) case params when Array @@ -63,7 +62,7 @@ module ActionDispatch end end - # Remove nils from the params hash + # Remove nils from the params hash. class NoNilParamEncoder < ParamEncoder # :nodoc: def self.handle_array(params) list = super diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index c554ce98bc..87dd1eba38 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -120,7 +120,7 @@ module ActionDispatch # controller :blog do # get 'blog/show' => :list # get 'blog/delete' => :delete - # get 'blog/edit' => :edit + # get 'blog/edit' => :edit # end # # # provides named routes for show, delete, and edit @@ -254,14 +254,5 @@ module ActionDispatch SEPARATORS = %w( / . ? ) #:nodoc: HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc: - - #:stopdoc: - INSECURE_URL_PARAMETERS_MESSAGE = <<-MSG.squish - Attempting to generate a URL from non-sanitized request parameters! - - An attacker can inject malicious data into the generated URL, such as - changing the host. Whitelist and sanitize passed parameters to be secure. - MSG - #:startdoc: end end diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index b91ffb8419..9aa4b92df2 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -196,7 +196,7 @@ module ActionDispatch @buffer << @view.render(partial: "routes/route", collection: routes) end - # the header is part of the HTML page, so we don't construct it here. + # The header is part of the HTML page, so we don't construct it here. def header(routes) end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index dea6c4482e..74904e3d45 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -17,9 +17,9 @@ module ActionDispatch CALL = ->(app, req) { app.call req.env } def initialize(app, constraints, strategy) - # Unwrap Constraints objects. I don't actually think it's possible + # Unwrap Constraints objects. I don't actually think it's possible # to pass a Constraints object to this constructor, but there were - # multiple places that kept testing children of this object. I + # multiple places that kept testing children of this object. I # *think* they were just being defensive, but I have no idea. if app.is_a?(self.class) constraints += app.constraints @@ -54,6 +54,7 @@ module ActionDispatch class Mapping #:nodoc: ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z} attr_reader :requirements, :defaults attr_reader :to, :default_controller, :default_action @@ -93,7 +94,7 @@ module ActionDispatch end def self.optional_format?(path, format) - format != false && !path.include?(":format") && !path.end_with?("/") + format != false && path !~ OPTIONAL_FORMAT_REGEX end def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options) @@ -218,7 +219,7 @@ module ActionDispatch private def add_wildcard_options(options, formatted, path_ast) # Add a constraint for wildcard route to make it non-greedy and match the - # optional format part of the route by default + # optional format part of the route by default. if formatted != false path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash| hash[node.name.to_sym] ||= /.+?/ @@ -396,7 +397,7 @@ module ActionDispatch end module Base - # Matches a url pattern to one or more routes. + # Matches a URL pattern to one or more routes. # # You should not use the +match+ method in your router # without specifying an HTTP method. @@ -406,7 +407,7 @@ module ActionDispatch # # sets :controller, :action and :id in params # match ':controller/:action/:id', via: [:get, :post] # - # Note that +:controller+, +:action+ and +:id+ are interpreted as url + # Note that +:controller+, +:action+ and +:id+ are interpreted as URL # query parameters and thus available through +params+ in an action. # # If you want to expose your action to GET, use +get+ in the router: @@ -455,7 +456,7 @@ module ActionDispatch # # === Options # - # Any options not seen here are passed on as params with the url. + # Any options not seen here are passed on as params with the URL. # # [:controller] # The route's controller. @@ -660,7 +661,7 @@ module ActionDispatch else prefix_options = options.slice(*_route.segment_keys) prefix_options[:relative_url_root] = "".freeze - # we must actually delete prefix segment keys to avoid passing them to next url_for + # We must actually delete prefix segment keys to avoid passing them to next url_for. _route.segment_keys.each { |k| options.delete(k) } _routes.url_helpers.send("#{name}_path", prefix_options) end @@ -1238,7 +1239,7 @@ module ActionDispatch # # resource :profile # - # creates six different routes in your application, all mapping to + # This creates six different routes in your application, all mapping to # the +Profiles+ controller (note that the controller is named after # the plural): # @@ -1323,14 +1324,14 @@ module ActionDispatch # # resources :posts, path_names: { new: "brand_new" } # - # The above example will now change /posts/new to /posts/brand_new + # The above example will now change /posts/new to /posts/brand_new. # # [:path] # Allows you to change the path prefix for the resource. # # resources :posts, path: 'postings' # - # The resource and all segments will now route to /postings instead of /posts + # The resource and all segments will now route to /postings instead of /posts. # # [:only] # Only generate routes for the given actions. @@ -1525,7 +1526,7 @@ module ActionDispatch end end - # See ActionDispatch::Routing::Mapper::Scoping#namespace + # See ActionDispatch::Routing::Mapper::Scoping#namespace. def namespace(path, options = {}) if resource_scope? nested { super } @@ -1545,7 +1546,7 @@ module ActionDispatch !parent_resource.singleton? && @scope[:shallow] end - # Matches a url pattern to one or more routes. + # Matches a URL pattern to one or more routes. # For more information, see match[rdoc-ref:Base#match]. # # match 'path' => 'controller#action', via: patch @@ -2003,7 +2004,7 @@ module ActionDispatch # concerns :commentable # end # - # concerns also work in any routes helper that you want to use: + # Concerns also work in any routes helper that you want to use: # # namespace :posts do # concerns :commentable @@ -2038,33 +2039,33 @@ module ActionDispatch # end # # The return value from the block passed to `direct` must be a valid set of - # arguments for `url_for` which will actually build the url string. This can + # arguments for `url_for` which will actually build the URL string. This can # be one of the following: # - # * A string, which is treated as a generated url + # * A string, which is treated as a generated URL # * A hash, e.g. { controller: "pages", action: "index" } # * An array, which is passed to `polymorphic_url` # * An Active Model instance # * An Active Model class # - # NOTE: Other url helpers can be called in the block but be careful not to invoke - # your custom url helper again otherwise it will result in a stack overflow error + # NOTE: Other URL helpers can be called in the block but be careful not to invoke + # your custom URL helper again otherwise it will result in a stack overflow error. # # You can also specify default options that will be passed through to - # your url helper definition, e.g: + # your URL helper definition, e.g: # # direct :browse, page: 1, size: 10 do |options| # [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ] # end # # In this instance the `params` object comes from the context in which the the - # block is executed, e.g. generating a url inside a controller action or a view. + # block is executed, e.g. generating a URL inside a controller action or a view. # If the block is executed where there isn't a params object such as this: # # Rails.application.routes.url_helpers.browse_path # # then it will raise a `NameError`. Because of this you need to be aware of the - # context in which you will use your custom url helper when defining it. + # context in which you will use your custom URL helper when defining it. # # NOTE: The `direct` method can't be used inside of a scope block such as # `namespace` or `scope` and will raise an error if it detects that it is. @@ -2076,7 +2077,7 @@ module ActionDispatch @set.add_url_helper(name, options, &block) end - # Define custom polymorphic mappings of models to urls. This alters the + # Define custom polymorphic mappings of models to URLs. This alters the # behavior of `polymorphic_url` and consequently the behavior of # `link_to` and `form_for` when passed a model instance, e.g: # @@ -2089,7 +2090,7 @@ module ActionDispatch # This will now generate "/basket" when a `Basket` instance is passed to # `link_to` or `form_for` instead of the standard "/baskets/:id". # - # NOTE: This custom behavior only applies to simple polymorphic urls where + # NOTE: This custom behavior only applies to simple polymorphic URLs where # a single model instance is passed and not more complicated forms, e.g: # # # config/routes.rb @@ -2105,7 +2106,7 @@ module ActionDispatch # link_to "Profile", [:admin, @current_user] # # The first `link_to` will generate "/profile" but the second will generate - # the standard polymorphic url of "/admin/users/1". + # the standard polymorphic URL of "/admin/users/1". # # You can pass options to a polymorphic mapping - the arity for the block # needs to be two as the instance is passed as the first argument, e.g: @@ -2114,9 +2115,9 @@ module ActionDispatch # [:basket, options] # end # - # This generates the url "/basket#items" because when the last item in an + # This generates the URL "/basket#items" because when the last item in an # array passed to `polymorphic_url` is a hash then it's treated as options - # to the url helper that gets called. + # to the URL helper that gets called. # # NOTE: The `resolve` method can't be used inside of a scope block such as # `namespace` or `scope` and will raise an error if it detects that it is. diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 984ded1ff5..e89ea8b21d 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -40,7 +40,7 @@ module ActionDispatch # # Example usage: # - # edit_polymorphic_path(@post) # => "/posts/1/edit" + # edit_polymorphic_path(@post) # => "/posts/1/edit" # polymorphic_path(@post, format: :pdf) # => "/posts/1.pdf" # # == Usage with mounted engines @@ -104,7 +104,7 @@ module ActionDispatch end if mapping = polymorphic_mapping(record_or_hash_or_array) - return mapping.call(self, [record_or_hash_or_array, options]) + return mapping.call(self, [record_or_hash_or_array, options], false) end opts = options.dup @@ -128,7 +128,7 @@ module ActionDispatch end if mapping = polymorphic_mapping(record_or_hash_or_array) - return mapping.call(self, [record_or_hash_or_array, options], only_path: true) + return mapping.call(self, [record_or_hash_or_array, options], true) end opts = options.dup @@ -273,7 +273,7 @@ module ActionDispatch def handle_model_call(target, record) if mapping = polymorphic_mapping(target, record) - mapping.call(target, [record], only_path: suffix == "path") + mapping.call(target, [record], suffix == "path") else method, args = handle_model(record) target.send(method, *args) diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index e8f47b8640..3bcb341758 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -146,7 +146,7 @@ module ActionDispatch # # get 'docs/:article', to: redirect('/wiki/%{article}') # - # Note that if you return a path without a leading slash then the url is prefixed with the + # Note that if you return a path without a leading slash then the URL is prefixed with the # current SCRIPT_NAME environment variable. This is typically '/' but may be different in # a mounted engine or where the application is deployed to a subdirectory of a website. # @@ -165,7 +165,7 @@ module ActionDispatch # Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass # the block to +get+ instead of +redirect+. Use <tt>{ ... }</tt> instead. # - # The options version of redirect allows you to supply only the parts of the url which need + # The options version of redirect allows you to supply only the parts of the URL which need # to change, it also supports interpolation of the path similar to the first example. # # get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}') diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 118dec2ad4..e1f9fc9ecc 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -164,13 +164,13 @@ module ActionDispatch @path_helpers_module.module_eval do define_method(:"#{name}_path") do |*args| - helper.call(self, args, only_path: true) + helper.call(self, args, true) end end @url_helpers_module.module_eval do define_method(:"#{name}_url") do |*args| - helper.call(self, args) + helper.call(self, args, false) end end end @@ -295,7 +295,7 @@ module ActionDispatch end private - # Create a url helper allowing ordered parameters to be associated + # Create a URL helper allowing ordered parameters to be associated # with corresponding dynamic segments, so you can do: # # foo_url(bar, baz, bang) @@ -318,11 +318,7 @@ module ActionDispatch when Hash args.pop when ActionController::Parameters - if last.permitted? - args.pop.to_h - else - raise ArgumentError, ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE - end + args.pop.to_h end helper.call self, args, options end @@ -509,6 +505,14 @@ module ActionDispatch @_proxy.url_for(options) end + def full_url_for(options) + @_proxy.full_url_for(options) + end + + def route_for(name, *args) + @_proxy.route_for(name, *args) + end + def optimize_routes_generation? @_proxy.optimize_routes_generation? end @@ -613,26 +617,14 @@ module ActionDispatch @block = block end - def call(t, args, outer_options = {}) + def call(t, args, only_path = false) options = args.extract_options! - url_options = eval_block(t, args, options) - - case url_options - when String - t.url_for(url_options) - when Hash - t.url_for(url_options.merge(outer_options)) - when ActionController::Parameters - if url_options.permitted? - t.url_for(url_options.to_h.merge(outer_options)) - else - raise ArgumentError, "Generating a URL from non sanitized request parameters is insecure!" - end - when Array - opts = url_options.extract_options! - t.url_for(url_options.push(opts.merge(outer_options))) + url = t.full_url_for(eval_block(t, args, options)) + + if only_path + "/" + url.partition(%r{(?<!/)/(?!/)}).last else - t.url_for([url_options, outer_options]) + url end end diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb index ee847eaeed..c1423f770f 100644 --- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb +++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb @@ -19,7 +19,8 @@ module ActionDispatch end end - def respond_to_missing?(method, include_private = false) + private + def respond_to_missing?(method, _) super || @helpers.respond_to?(method) end @@ -32,7 +33,7 @@ module ActionDispatch @helpers.#{method}(*args) end RUBY - send(method, *args) + public_send(method, *args) else super end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 3e564f13d8..a9bdefa775 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -113,10 +113,10 @@ module ActionDispatch default_url_options end - # Generate a url based on the options provided, default_url_options and the + # Generate a URL based on the options provided, default_url_options and the # routes defined in routes.rb. The following options are supported: # - # * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+. + # * <tt>:only_path</tt> - If true, the relative URL is returned. Defaults to +false+. # * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'. # * <tt>:host</tt> - Specifies the host the link should be targeted at. # If <tt>:only_path</tt> is false, this option must be @@ -164,20 +164,17 @@ module ActionDispatch # implicitly used by +url_for+ can always be overwritten like shown on the # last +url_for+ calls. def url_for(options = nil) + full_url_for(options) + end + + def full_url_for(options = nil) # :nodoc: case options when nil _routes.url_for(url_options.symbolize_keys) - when Hash - route_name = options.delete :use_route - _routes.url_for(options.symbolize_keys.reverse_merge!(url_options), - route_name) - when ActionController::Parameters - unless options.permitted? - raise ArgumentError.new(ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE) - end + when Hash, ActionController::Parameters route_name = options.delete :use_route - _routes.url_for(options.to_h.symbolize_keys. - reverse_merge!(url_options), route_name) + merged_url_options = options.to_h.symbolize_keys.reverse_merge!(url_options) + _routes.url_for(merged_url_options, route_name) when String options when Symbol @@ -192,6 +189,10 @@ module ActionDispatch end end + def route_for(name, *args) # :nodoc: + public_send(:"#{name}_url", *args) + end + protected def optimize_routes_generation? diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb index 1bf47d2556..98fdb36c91 100644 --- a/actionpack/lib/action_dispatch/system_test_case.rb +++ b/actionpack/lib/action_dispatch/system_test_case.rb @@ -1,4 +1,5 @@ require "capybara/dsl" +require "capybara/minitest" require "action_controller" require "action_dispatch/system_testing/driver" require "action_dispatch/system_testing/server" @@ -48,7 +49,7 @@ module ActionDispatch # By default, <tt>ActionDispatch::SystemTestCase</tt> is driven by the # Selenium driver, with the Chrome browser, and a browser size of 1400x1400. # - # Changing the driver configuration options are easy. Let's say you want to use + # Changing the driver configuration options is easy. Let's say you want to use # the Firefox browser instead of Chrome. In your +application_system_test_case.rb+ # file add the following: # @@ -80,12 +81,13 @@ module ActionDispatch # tests as long as you include the required gems and files. class SystemTestCase < IntegrationTest include Capybara::DSL + include Capybara::Minitest::Assertions include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super - self.class.superclass.driver.use + self.class.driver.use end def self.start_application # :nodoc: @@ -98,6 +100,8 @@ module ActionDispatch SystemTesting::Server.new.run end + class_attribute :driver, instance_accessor: false + # System Test configuration options # # The default settings are Selenium, using Chrome, with a screen size @@ -110,14 +114,11 @@ module ActionDispatch # driven_by :selenium, using: :firefox # # driven_by :selenium, screen_size: [800, 800] - def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400]) - @driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size) + def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {}) + self.driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size, options: options) end - # Returns the driver object for the initialized system test - def self.driver - @driver ||= SystemTestCase.driven_by(:selenium) - end + driven_by :selenium end SystemTestCase.start_application diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb index 72d132d64f..5cf17883f7 100644 --- a/actionpack/lib/action_dispatch/system_testing/driver.rb +++ b/actionpack/lib/action_dispatch/system_testing/driver.rb @@ -5,6 +5,7 @@ module ActionDispatch @name = name @browser = options[:using] @screen_size = options[:screen_size] + @options = options[:options] end def use @@ -19,7 +20,7 @@ module ActionDispatch def register Capybara.register_driver @name do |app| - Capybara::Selenium::Driver.new(app, browser: @browser).tap do |driver| + Capybara::Selenium::Driver.new(app, { browser: @browser }.merge(@options)).tap do |driver| driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size) end end diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb index 3078e035a3..859d68e475 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb @@ -1,7 +1,7 @@ module ActionDispatch module SystemTesting module TestHelpers - # Screenshot helper for system testing + # Screenshot helper for system testing. module ScreenshotHelper # Takes a screenshot of the current page in the browser. # diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb index 187ba2cc5f..f03f0d4299 100644 --- a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb @@ -2,7 +2,12 @@ module ActionDispatch module SystemTesting module TestHelpers module SetupAndTeardown # :nodoc: - DEFAULT_HOST = "127.0.0.1" + DEFAULT_HOST = "http://127.0.0.1" + + def host!(host) + super + Capybara.app_host = host + end def before_setup host! DEFAULT_HOST diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 817737341c..1baf979ac9 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -45,7 +45,7 @@ module ActionDispatch # # Asserts that the redirection was to the named route login_url # assert_redirected_to login_url # - # # Asserts that the redirection was to the url for @customer + # # Asserts that the redirection was to the URL for @customer # assert_redirected_to @customer # # # Asserts that the redirection matches the regular expression diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 37c1ca02b6..8645df4370 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -18,8 +18,8 @@ module ActionDispatch # assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post}) # # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used - # to assert that values in the query string will end up in the params hash correctly. To test query strings you must use the - # extras argument, appending the query string on the path directly will not work. For example: + # to assert that values in the query string will end up in the params hash correctly. To test query strings you must use the extras + # argument because appending the query string on the path directly will not work. For example: # # # Asserts that a path of '/items/list/1?view=print' returns the correct options # assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" }) @@ -132,8 +132,7 @@ module ActionDispatch end # A helper to make it easier to test different route configurations. - # This method temporarily replaces @routes - # with a new RouteSet instance. + # This method temporarily replaces @routes with a new RouteSet instance. # # The new instance is yielded to the passed block. Typically the block # will create some routes using <tt>set.draw { match ... }</tt>: @@ -186,7 +185,6 @@ module ActionDispatch method = :get end - # Assume given controller request = ActionController::TestRequest.create @controller.class if path =~ %r{://} diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index a3430e210e..2416c58817 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -192,11 +192,10 @@ module ActionDispatch # HTTP methods in integration tests. +#process+ is only required when using a # request method that doesn't have a method defined in the integration tests. # - # This method returns a Response object, which one can use to - # inspect the details of the response. Furthermore, if this method was - # called from an ActionDispatch::IntegrationTest object, then that - # object's <tt>@response</tt> instance variable will point to the same - # response object. + # This method returns the response status, after performing the request. + # Furthermore, if this method was called from an ActionDispatch::IntegrationTest object, + # then that object's <tt>@response</tt> instance variable will point to a Response object + # which one can use to inspect the details of the response. # # Example: # process :get, '/author', params: { since: 201501011400 } @@ -247,7 +246,7 @@ module ActionDispatch wrapped_headers["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ") end - # this modifies the passed request_env directly + # This modifies the passed request_env directly. if wrapped_headers.present? Http::Headers.from_hash(request_env).merge!(wrapped_headers) end @@ -258,7 +257,7 @@ module ActionDispatch session = Rack::Test::Session.new(_mock_session) # NOTE: rack-test v0.5 doesn't build a default uri correctly - # Make sure requested path is always a full uri + # Make sure requested path is always a full URI. session.request(build_full_uri(path, request_env), request_env) @request_count += 1 @@ -325,8 +324,8 @@ module ActionDispatch def create_session(app) klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) { - # If the app is a Rails app, make url_helpers available on the session - # This makes app.url_for and app.foo_path available in the console + # If the app is a Rails app, make url_helpers available on the session. + # This makes app.url_for and app.foo_path available in the console. if app.respond_to?(:routes) include app.routes.url_helpers include app.routes.mounted_helpers @@ -386,14 +385,15 @@ module ActionDispatch integration_session.default_url_options = options end - def respond_to_missing?(method, include_private = false) - integration_session.respond_to?(method, include_private) || super + private + def respond_to_missing?(method, _) + integration_session.respond_to?(method) || super end # Delegate unhandled messages to the current session instance. - def method_missing(sym, *args, &block) - if integration_session.respond_to?(sym) - integration_session.__send__(sym, *args, &block).tap do + def method_missing(method, *args, &block) + if integration_session.respond_to?(method) + integration_session.public_send(method, *args, &block).tap do copy_session_variables! end else diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index 91b25ec155..ec949c869b 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -9,7 +9,7 @@ module ActionDispatch "HTTP_USER_AGENT" => "Rails Testing", ) - # Create a new test request with default `env` values + # Create a new test request with default `env` values. def self.create(env = {}) env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application env["rack.request.cookie_hash"] ||= {}.with_indifferent_access diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb index d6a91a0569..fddc3033d5 100644 --- a/actionpack/lib/action_pack/gem_version.rb +++ b/actionpack/lib/action_pack/gem_version.rb @@ -6,9 +6,9 @@ module ActionPack module VERSION MAJOR = 5 - MINOR = 1 + MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 4185ce1a1f..bd118b46be 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -1,6 +1,6 @@ -$:.unshift(File.dirname(__FILE__) + "/lib") -$:.unshift(File.dirname(__FILE__) + "/fixtures/helpers") -$:.unshift(File.dirname(__FILE__) + "/fixtures/alternate_helpers") +$:.unshift File.expand_path("lib", __dir__) +$:.unshift File.expand_path("fixtures/helpers", __dir__) +$:.unshift File.expand_path("fixtures/alternate_helpers", __dir__) require "active_support/core_ext/kernel/reporting" @@ -56,7 +56,7 @@ ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false -FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), "fixtures") +FIXTURE_LOAD_PATH = File.join(__dir__, "fixtures") SharedTestRoutes = ActionDispatch::Routing::RouteSet.new @@ -156,7 +156,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase end def with_autoload_path(path) - path = File.join(File.dirname(__FILE__), "fixtures", path) + path = File.join(__dir__, "fixtures", path) if ActiveSupport::Dependencies.autoload_paths.include?(path) yield else diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 9ab152fc5c..73aab5848b 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -83,7 +83,7 @@ class ActionPackAssertionsController < ActionController::Base end def render_file_absolute_path - render file: File.expand_path("../../../README.rdoc", __FILE__) + render file: File.expand_path("../../README.rdoc", __dir__) end def render_file_relative_path diff --git a/actionpack/test/controller/api/data_streaming_test.rb b/actionpack/test/controller/api/data_streaming_test.rb index f15b78d102..e6419b9adf 100644 --- a/actionpack/test/controller/api/data_streaming_test.rb +++ b/actionpack/test/controller/api/data_streaming_test.rb @@ -1,7 +1,7 @@ require "abstract_unit" module TestApiFileUtils - def file_path() File.expand_path(__FILE__) end + def file_path() __FILE__ end def file_data() @data ||= File.open(file_path, "rb") { |f| f.read } end end diff --git a/actionpack/test/controller/api/with_helpers_test.rb b/actionpack/test/controller/api/with_helpers_test.rb new file mode 100644 index 0000000000..06db949153 --- /dev/null +++ b/actionpack/test/controller/api/with_helpers_test.rb @@ -0,0 +1,42 @@ +require "abstract_unit" + +module ApiWithHelper + def my_helper + "helper" + end +end + +class WithHelpersController < ActionController::API + include ActionController::Helpers + helper ApiWithHelper + + def with_helpers + render plain: self.class.helpers.my_helper + end +end + +class SubclassWithHelpersController < WithHelpersController + def with_helpers + render plain: self.class.helpers.my_helper + end +end + +class WithHelpersTest < ActionController::TestCase + tests WithHelpersController + + def test_with_helpers + get :with_helpers + + assert_equal "helper", response.body + end +end + +class SubclassWithHelpersTest < ActionController::TestCase + tests WithHelpersController + + def test_with_helpers + get :with_helpers + + assert_equal "helper", response.body + end +end diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 42a5157010..4e969fac07 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -11,6 +11,12 @@ end class EmptyController < ActionController::Base end +class SimpleController < ActionController::Base + def hello + self.response_body = "hello" + end +end + class NonEmptyController < ActionController::Base def public_action head :ok @@ -118,6 +124,27 @@ class ControllerInstanceTests < ActiveSupport::TestCase controller = klass.new assert_equal "examples", controller.controller_path end + + def test_response_has_default_headers + original_default_headers = ActionDispatch::Response.default_headers + + ActionDispatch::Response.default_headers = { + "X-Frame-Options" => "DENY", + "X-Content-Type-Options" => "nosniff", + "X-XSS-Protection" => "1;" + } + + response_headers = SimpleController.action("hello").call( + "REQUEST_METHOD" => "GET", + "rack.input" => -> {} + )[1] + + assert response_headers.key?("X-Frame-Options") + assert response_headers.key?("X-Content-Type-Options") + assert response_headers.key?("X-XSS-Protection") + ensure + ActionDispatch::Response.default_headers = original_default_headers + end end class PerformActionTest < ActionController::TestCase diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index fa8d9dc09a..c86dcafee5 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -4,7 +4,7 @@ require "lib/controller/fake_models" CACHE_DIR = "test_cache" # Don't change '/../temp/' cavalierly or you might hose something you don't want hosed -FILE_STORE_PATH = File.join(File.dirname(__FILE__), "/../temp/", CACHE_DIR) +FILE_STORE_PATH = File.join(__dir__, "../temp/", CACHE_DIR) class FragmentCachingMetalTestController < ActionController::Metal abstract! @@ -26,10 +26,6 @@ class FragmentCachingMetalTest < ActionController::TestCase @controller.request = @request @controller.response = @response end - - def test_fragment_cache_key - assert_equal "views/what a key", @controller.fragment_cache_key("what a key") - end end class CachingController < ActionController::Base @@ -43,6 +39,8 @@ class FragmentCachingTestController < CachingController end class FragmentCachingTest < ActionController::TestCase + ModelWithKeyAndVersion = Struct.new(:cache_key, :cache_version) + def setup super @store = ActiveSupport::Cache::MemoryStore.new @@ -53,12 +51,25 @@ class FragmentCachingTest < ActionController::TestCase @controller.params = @params @controller.request = @request @controller.response = @response + + @m1v1 = ModelWithKeyAndVersion.new("model/1", "1") + @m1v2 = ModelWithKeyAndVersion.new("model/1", "2") + @m2v1 = ModelWithKeyAndVersion.new("model/2", "1") + @m2v2 = ModelWithKeyAndVersion.new("model/2", "2") end def test_fragment_cache_key - assert_equal "views/what a key", @controller.fragment_cache_key("what a key") - assert_equal "views/test.host/fragment_caching_test/some_action", - @controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action") + assert_deprecated do + assert_equal "views/what a key", @controller.fragment_cache_key("what a key") + assert_equal "views/test.host/fragment_caching_test/some_action", + @controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action") + end + end + + def test_combined_fragment_cache_key + assert_equal [ :views, "what a key" ], @controller.combined_fragment_cache_key("what a key") + assert_equal [ :views, "test.host/fragment_caching_test/some_action" ], + @controller.combined_fragment_cache_key(controller: "fragment_caching_test", action: "some_action") end def test_read_fragment_with_caching_enabled @@ -72,6 +83,12 @@ class FragmentCachingTest < ActionController::TestCase assert_nil @controller.read_fragment("name") end + def test_read_fragment_with_versioned_model + @controller.write_fragment([ "stuff", @m1v1 ], "hello") + assert_equal "hello", @controller.read_fragment([ "stuff", @m1v1 ]) + assert_nil @controller.read_fragment([ "stuff", @m1v2 ]) + end + def test_fragment_exist_with_caching_enabled @store.write("views/name", "value") assert @controller.fragment_exist?("name") @@ -198,7 +215,7 @@ CACHED assert_equal expected_body, @response.body assert_equal "This bit's fragment cached", - @store.read("views/test.host/functional_caching/fragment_cached/#{template_digest("functional_caching/fragment_cached")}") + @store.read("views/functional_caching/fragment_cached:#{template_digest("functional_caching/fragment_cached")}/fragment") end def test_fragment_caching_in_partials @@ -207,7 +224,7 @@ CACHED assert_match(/Old fragment caching in a partial/, @response.body) assert_match("Old fragment caching in a partial", - @store.read("views/test.host/functional_caching/html_fragment_cached_with_partial/#{template_digest("functional_caching/_partial")}")) + @store.read("views/functional_caching/_partial:#{template_digest("functional_caching/_partial")}/test.host/functional_caching/html_fragment_cached_with_partial")) end def test_skipping_fragment_cache_digesting @@ -237,7 +254,7 @@ CACHED assert_match(/Some inline content/, @response.body) assert_match(/Some cached content/, @response.body) assert_match("Some cached content", - @store.read("views/test.host/functional_caching/inline_fragment_cached/#{template_digest("functional_caching/inline_fragment_cached")}")) + @store.read("views/functional_caching/inline_fragment_cached:#{template_digest("functional_caching/inline_fragment_cached")}/test.host/functional_caching/inline_fragment_cached")) end def test_fragment_cache_instrumentation @@ -264,7 +281,7 @@ CACHED assert_equal expected_body, @response.body assert_equal "<p>ERB</p>", - @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}") + @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment") end def test_xml_formatted_fragment_caching @@ -275,7 +292,7 @@ CACHED assert_equal expected_body, @response.body assert_equal " <p>Builder</p>\n", - @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}") + @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment") end def test_fragment_caching_with_variant @@ -286,7 +303,7 @@ CACHED assert_equal expected_body, @response.body assert_equal "<p>PHONE</p>", - @store.read("views/test.host/functional_caching/formatted_fragment_cached_with_variant/#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}") + @store.read("views/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}/fragment") end private @@ -412,7 +429,7 @@ class CollectionCacheTest < ActionController::TestCase def test_collection_fetches_cached_views get :index assert_equal 1, @controller.partial_rendered_times - assert_customer_cached "david/1", "david, 1" + assert_match "david, 1", ActionView::PartialRenderer.collection_cache.read("views/customers/_customer:7c228ab609f0baf0b1f2367469210937/david/1") get :index assert_equal 1, @controller.partial_rendered_times @@ -444,14 +461,8 @@ class CollectionCacheTest < ActionController::TestCase def test_caching_with_callable_cache_key get :index_with_callable_cache_key - assert_customer_cached "cached_david", "david, 1" + assert_match "david, 1", ActionView::PartialRenderer.collection_cache.read("views/customers/_customer:7c228ab609f0baf0b1f2367469210937/cached_david") end - - private - def assert_customer_cached(key, content) - assert_match content, - ActionView::PartialRenderer.collection_cache.read("views/#{key}/7c228ab609f0baf0b1f2367469210937") - end end class FragmentCacheKeyTestController < CachingController @@ -470,11 +481,21 @@ class FragmentCacheKeyTest < ActionController::TestCase @controller.cache_store = @store end - def test_fragment_cache_key + def test_combined_fragment_cache_key @controller.account_id = "123" - assert_equal "views/v1/123/what a key", @controller.fragment_cache_key("what a key") + assert_equal [ :views, "v1", "123", "what a key" ], @controller.combined_fragment_cache_key("what a key") @controller.account_id = nil - assert_equal "views/v1//what a key", @controller.fragment_cache_key("what a key") + assert_equal [ :views, "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key") + end + + def test_combined_fragment_cache_key_with_envs + ENV["RAILS_APP_VERSION"] = "55" + assert_equal [ :views, "55", "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key") + + ENV["RAILS_CACHE_ID"] = "66" + assert_equal [ :views, "66", "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key") + ensure + ENV["RAILS_CACHE_ID"] = ENV["RAILS_APP_VERSION"] = nil end end diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index 4c6a772062..03dbd63614 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -1,6 +1,6 @@ require "abstract_unit" -ActionController::Base.helpers_path = File.expand_path("../../fixtures/helpers", __FILE__) +ActionController::Base.helpers_path = File.expand_path("../fixtures/helpers", __dir__) module Fun class GamesController < ActionController::Base @@ -48,7 +48,7 @@ end class HelpersPathsController < ActionController::Base paths = ["helpers2_pack", "helpers1_pack"].map do |path| - File.join(File.expand_path("../../fixtures", __FILE__), path) + File.join(File.expand_path("../fixtures", __dir__), path) end $:.unshift(*paths) @@ -61,7 +61,7 @@ class HelpersPathsController < ActionController::Base end class HelpersTypoController < ActionController::Base - path = File.expand_path("../../fixtures/helpers_typo", __FILE__) + path = File.expand_path("../fixtures/helpers_typo", __dir__) $:.unshift(path) self.helpers_path = path end @@ -178,7 +178,7 @@ class HelperTest < ActiveSupport::TestCase end def test_all_helpers_with_alternate_helper_dir - @controller_class.helpers_path = File.expand_path("../../fixtures/alternate_helpers", __FILE__) + @controller_class.helpers_path = File.expand_path("../fixtures/alternate_helpers", __dir__) # Reload helpers @controller_class._helpers = Module.new diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 57f58fd835..72163ccd5e 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -1091,7 +1091,7 @@ class IntegrationFileUploadTest < ActionDispatch::IntegrationTest end def self.fixture_path - File.dirname(__FILE__) + "/../fixtures/multipart" + File.expand_path("../fixtures/multipart", __dir__) end routes.draw do diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 581081dd07..bfb47b90d5 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -152,7 +152,7 @@ module ActionController end def write_sleep_autoload - path = File.join(File.dirname(__FILE__), "../fixtures") + path = File.expand_path("../fixtures", __dir__) ActiveSupport::Dependencies.autoload_paths << path response.headers["Content-Type"] = "text/event-stream" diff --git a/actionpack/test/controller/metal_test.rb b/actionpack/test/controller/metal_test.rb new file mode 100644 index 0000000000..e16452ed6f --- /dev/null +++ b/actionpack/test/controller/metal_test.rb @@ -0,0 +1,30 @@ +require "abstract_unit" + +class MetalControllerInstanceTests < ActiveSupport::TestCase + class SimpleController < ActionController::Metal + def hello + self.response_body = "hello" + end + end + + def test_response_has_default_headers + original_default_headers = ActionDispatch::Response.default_headers + + ActionDispatch::Response.default_headers = { + "X-Frame-Options" => "DENY", + "X-Content-Type-Options" => "nosniff", + "X-XSS-Protection" => "1;" + } + + response_headers = SimpleController.action("hello").call( + "REQUEST_METHOD" => "GET", + "rack.input" => -> {} + )[1] + + refute response_headers.key?("X-Frame-Options") + refute response_headers.key?("X-Content-Type-Options") + refute response_headers.key?("X-XSS-Protection") + ensure + ActionDispatch::Response.default_headers = original_default_headers + end +end diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb index a22fa39051..d1c4dbfef7 100644 --- a/actionpack/test/controller/mime/accept_format_test.rb +++ b/actionpack/test/controller/mime/accept_format_test.rb @@ -29,7 +29,7 @@ class StarStarMimeControllerTest < ActionController::TestCase end class AbstractPostController < ActionController::Base - self.view_paths = File.dirname(__FILE__) + "/../../fixtures/post_test/" + self.view_paths = File.expand_path("../../fixtures/post_test", __dir__) end # For testing layouts which are set automatically diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb index 6d651e0104..4491dd96ed 100644 --- a/actionpack/test/controller/new_base/render_file_test.rb +++ b/actionpack/test/controller/new_base/render_file_test.rb @@ -2,15 +2,15 @@ require "abstract_unit" module RenderFile class BasicController < ActionController::Base - self.view_paths = File.dirname(__FILE__) + self.view_paths = __dir__ def index - render file: File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world]) + render file: File.expand_path("../../fixtures/test/hello_world", __dir__) end def with_instance_variables @secret = "in the sauce" - render file: File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_ivar") + render file: File.expand_path("../../fixtures/test/render_file_with_ivar", __dir__) end def relative_path @@ -25,11 +25,11 @@ module RenderFile def pathname @secret = "in the sauce" - render file: Pathname.new(File.dirname(__FILE__)).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar]) + render file: Pathname.new(__dir__).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar]) end def with_locals - path = File.join(File.dirname(__FILE__), "../../fixtures/test/render_file_with_locals") + path = File.expand_path("../../fixtures/test/render_file_with_locals", __dir__) render file: path, locals: { secret: "in the sauce" } end end diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb index 796283466a..c5fc8e15e1 100644 --- a/actionpack/test/controller/new_base/render_implicit_action_test.rb +++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb @@ -6,7 +6,7 @@ module RenderImplicitAction "render_implicit_action/simple/hello_world.html.erb" => "Hello world!", "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!", "render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented" - ), ActionView::FileSystemResolver.new(File.expand_path("../../../controller", __FILE__))] + ), ActionView::FileSystemResolver.new(File.expand_path("../../controller", __dir__))] def hello_world() end end diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb index f17e93a431..7725c25e22 100644 --- a/actionpack/test/controller/parameters/accessors_test.rb +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -99,7 +99,7 @@ class ParametersAccessorsTest < ActiveSupport::TestCase test "has_value? returns false if the given value is not present in the params" do params = ActionController::Parameters.new(city: "Chicago", state: "Illinois") - refute @params.has_value?("New York") + refute params.has_value?("New York") end test "include? returns true if the given key is present in the params" do diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb index e61bbdbe13..2c36f488c6 100644 --- a/actionpack/test/controller/parameters/mutators_test.rb +++ b/actionpack/test/controller/parameters/mutators_test.rb @@ -25,6 +25,27 @@ class ParametersMutatorsTest < ActiveSupport::TestCase assert_not @params.delete(:person).permitted? end + test "delete returns the value when the key is present" do + assert_equal "32", @params[:person].delete(:age) + end + + test "delete removes the entry when the key present" do + @params[:person].delete(:age) + assert_not @params[:person].key?(:age) + end + + test "delete returns nil when the key is not present" do + assert_equal nil, @params[:person].delete(:first_name) + end + + test "delete returns the value of the given block when the key is not present" do + assert_equal "David", @params[:person].delete(:first_name) { "David" } + end + + test "delete yields the key to the given block when the key is not present" do + assert_equal "first_name: David", @params[:person].delete(:first_name) { |k| "#{k}: David" } + end + test "delete_if retains permitted status" do @params.permit! assert @params.delete_if { |k| k == "person" }.permitted? diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 9f3025587e..ae2b45c9f0 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -310,6 +310,14 @@ class ParametersPermitTest < ActiveSupport::TestCase refute_predicate merged_params[:person], :empty? end + test "#with_defaults is an alias of reverse_merge" do + default_params = ActionController::Parameters.new(id: "1234", person: {}).permit! + merged_params = @params.with_defaults(default_params) + + assert_equal "1234", merged_params[:id] + refute_predicate merged_params[:person], :empty? + end + test "not permitted is sticky beyond reverse_merge" do refute_predicate @params.reverse_merge(a: "b"), :permitted? end @@ -327,6 +335,14 @@ class ParametersPermitTest < ActiveSupport::TestCase refute_predicate @params[:person], :empty? end + test "#with_defaults! is an alias of reverse_merge!" do + default_params = ActionController::Parameters.new(id: "1234", person: {}).permit! + @params.with_defaults!(default_params) + + assert_equal "1234", @params[:id] + refute_predicate @params[:person], :empty? + end + test "modifying the parameters" do @params[:person][:hometown] = "Chicago" @params[:person][:family] = { brother: "Jonas" } @@ -361,17 +377,17 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal "32", @params[:person].permit([ :age ])[:age] end - test "to_h returns empty hash on unpermitted params" do - assert @params.to_h.is_a? ActiveSupport::HashWithIndifferentAccess - assert_not @params.to_h.is_a? ActionController::Parameters - assert @params.to_h.empty? + test "to_h raises UnfilteredParameters on unfiltered params" do + assert_raises(ActionController::UnfilteredParameters) do + @params.to_h + end end test "to_h returns converted hash on permitted params" do @params.permit! - assert @params.to_h.is_a? ActiveSupport::HashWithIndifferentAccess - assert_not @params.to_h.is_a? ActionController::Parameters + assert_instance_of ActiveSupport::HashWithIndifferentAccess, @params.to_h + assert_not_kind_of ActionController::Parameters, @params.to_h end test "to_h returns converted hash when .permit_all_parameters is set" do @@ -379,39 +395,71 @@ class ParametersPermitTest < ActiveSupport::TestCase ActionController::Parameters.permit_all_parameters = true params = ActionController::Parameters.new(crab: "Senjougahara Hitagi") - assert params.to_h.is_a? ActiveSupport::HashWithIndifferentAccess - assert_not @params.to_h.is_a? ActionController::Parameters + assert_instance_of ActiveSupport::HashWithIndifferentAccess, params.to_h + assert_not_kind_of ActionController::Parameters, params.to_h assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_h) ensure ActionController::Parameters.permit_all_parameters = false end end - test "to_h returns always permitted parameter on unpermitted params" do - params = ActionController::Parameters.new( - controller: "users", - action: "create", - user: { - name: "Sengoku Nadeko" - } - ) + test "to_hash raises UnfilteredParameters on unfiltered params" do + assert_raises(ActionController::UnfilteredParameters) do + @params.to_hash + end + end + + test "to_hash returns converted hash on permitted params" do + @params.permit! + + assert_instance_of Hash, @params.to_hash + assert_not_kind_of ActionController::Parameters, @params.to_hash + end + + test "parameters can be implicit converted to Hash" do + params = ActionController::Parameters.new + params.permit! - assert_equal({ "controller" => "users", "action" => "create" }, params.to_h) + assert_equal({ a: 1 }, { a: 1 }.merge!(params)) + end + + test "to_hash returns converted hash when .permit_all_parameters is set" do + begin + ActionController::Parameters.permit_all_parameters = true + params = ActionController::Parameters.new(crab: "Senjougahara Hitagi") + + assert_instance_of Hash, params.to_hash + assert_not_kind_of ActionController::Parameters, params.to_hash + assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_hash) + assert_equal({ "crab" => "Senjougahara Hitagi" }, params) + ensure + ActionController::Parameters.permit_all_parameters = false + end end test "to_unsafe_h returns unfiltered params" do - assert @params.to_unsafe_h.is_a? ActiveSupport::HashWithIndifferentAccess - assert_not @params.to_unsafe_h.is_a? ActionController::Parameters + assert_instance_of ActiveSupport::HashWithIndifferentAccess, @params.to_unsafe_h + assert_not_kind_of ActionController::Parameters, @params.to_unsafe_h end test "to_unsafe_h returns unfiltered params even after accessing few keys" do params = ActionController::Parameters.new("f" => { "language_facet" => ["Tibetan"] }) expected = { "f" => { "language_facet" => ["Tibetan"] } } - assert params["f"].is_a? ActionController::Parameters + assert_instance_of ActionController::Parameters, params["f"] assert_equal expected, params.to_unsafe_h end + test "to_unsafe_h does not mutate the parameters" do + params = ActionController::Parameters.new("f" => { "language_facet" => ["Tibetan"] }) + params[:f] + + params.to_unsafe_h + + assert_not_predicate params, :permitted? + assert_not_predicate params[:f], :permitted? + end + test "to_h only deep dups Ruby collections" do company = Class.new do attr_reader :dupped diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index faa57c4559..2a41d57b26 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -32,6 +32,10 @@ class ParamsWrapperTest < ActionController::TestCase def self.attribute_names [] end + + def self.stored_attributes + { settings: [:color, :size] } + end end class Person @@ -62,6 +66,17 @@ class ParamsWrapperTest < ActionController::TestCase end end + def test_store_accessors_wrapped + assert_called(User, :attribute_names, times: 2, returns: ["username"]) do + with_default_wrapper_options do + @request.env["CONTENT_TYPE"] = "application/json" + post :parse, params: { "username" => "sikachu", "color" => "blue", "size" => "large" } + assert_parameters("username" => "sikachu", "color" => "blue", "size" => "large", + "user" => { "username" => "sikachu", "color" => "blue", "size" => "large" }) + end + end + end + def test_specify_wrapper_name with_default_wrapper_options do UsersController.wrap_parameters :person diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index f06a1f4d23..5b16af78c4 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -285,10 +285,10 @@ class RedirectTest < ActionController::TestCase end def test_redirect_to_params - error = assert_raise(ArgumentError) do + error = assert_raise(ActionController::UnfilteredParameters) do get :redirect_to_params end - assert_equal ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE, error.message + assert_equal "unable to convert unpermitted parameters to hash", error.message end def test_redirect_to_with_block diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 3a0a0a8bde..17d834d55f 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -257,7 +257,7 @@ end module TemplateModificationHelper private def modify_template(name) - path = File.expand_path("../../fixtures/#{name}.erb", __FILE__) + path = File.expand_path("../fixtures/#{name}.erb", __dir__) original = File.read(path) File.write(path, "#{original} Modified!") ActionView::LookupContext::DetailsKey.clear @@ -287,9 +287,9 @@ class ExpiresInRenderTest < ActionController::TestCase def test_dynamic_render_with_file # This is extremely bad, but should be possible to do. - assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")) + assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__)) response = get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' } - assert_equal File.read(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")), + assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)), response.body end @@ -306,16 +306,16 @@ class ExpiresInRenderTest < ActionController::TestCase end def test_dynamic_render - assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")) + assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__)) assert_raises ActionView::MissingTemplate do get :dynamic_render, params: { id: '../\\../test/abstract_unit.rb' } end end def test_permitted_dynamic_render_file_hash - assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")) + assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__)) response = get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } } - assert_equal File.read(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")), + assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)), response.body end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index d645ddfdbe..521d93f02e 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -35,6 +35,22 @@ module RequestForgeryProtectionActions render inline: "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>" end + def form_with_remote + render inline: "<%= form_with(scope: :some_resource) {} %>" + end + + def form_with_remote_with_token + render inline: "<%= form_with(scope: :some_resource, authenticity_token: true) {} %>" + end + + def form_with_local_with_token + render inline: "<%= form_with(scope: :some_resource, local: true, authenticity_token: true) {} %>" + end + + def form_with_remote_with_external_token + render inline: "<%= form_with(scope: :some_resource, authenticity_token: 'external_token') {} %>" + end + def same_origin_js render js: "foo();" end @@ -235,6 +251,80 @@ module RequestForgeryProtectionTests end end + def test_should_render_form_with_with_token_tag_if_remote + assert_not_blocked do + get :form_with_remote + end + assert_match(/authenticity_token/, response.body) + end + + def test_should_render_form_with_without_token_tag_if_remote_and_embedding_token_is_off + original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms + begin + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = false + assert_not_blocked do + get :form_with_remote + end + assert_no_match(/authenticity_token/, response.body) + ensure + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original + end + end + + def test_should_render_form_with_with_token_tag_if_remote_and_external_authenticity_token_requested_and_embedding_is_on + original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms + begin + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true + assert_not_blocked do + get :form_with_remote_with_external_token + end + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", "external_token" + ensure + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original + end + end + + def test_should_render_form_with_with_token_tag_if_remote_and_external_authenticity_token_requested + assert_not_blocked do + get :form_with_remote_with_external_token + end + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", "external_token" + end + + def test_should_render_form_with_with_token_tag_if_remote_and_authenticity_token_requested + @controller.stub :form_authenticity_token, @token do + assert_not_blocked do + get :form_with_remote_with_token + end + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token + end + end + + def test_should_render_form_with_with_token_tag_with_authenticity_token_requested + @controller.stub :form_authenticity_token, @token do + assert_not_blocked do + get :form_with_local_with_token + end + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token + end + end + + def test_should_render_form_with_with_token_tag_if_remote_and_embedding_token_is_on + original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms + begin + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true + + @controller.stub :form_authenticity_token, @token do + assert_not_blocked do + get :form_with_remote + end + end + assert_select "form>input[name=?][value=?]", "custom_authenticity_token", @token + ensure + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original + end + end + def test_should_allow_get assert_not_blocked { get :index } end @@ -347,6 +437,10 @@ module RequestForgeryProtectionTests end def test_should_block_post_with_origin_checking_and_wrong_origin + old_logger = ActionController::Base.logger + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + ActionController::Base.logger = logger + forgery_protection_origin_check do session[:_csrf_token] = @token @controller.stub :form_authenticity_token, @token do @@ -356,6 +450,13 @@ module RequestForgeryProtectionTests end end end + + assert_match( + "HTTP Origin header (http://bad.host) didn't match request.base_url (http://test.host)", + logger.logged(:warn).last + ) + ensure + ActionController::Base.logger = old_logger end def test_should_warn_on_missing_csrf_token diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb index dd07c2486b..46bb374b3f 100644 --- a/actionpack/test/controller/required_params_test.rb +++ b/actionpack/test/controller/required_params_test.rb @@ -72,9 +72,27 @@ class ParametersRequireTest < ActiveSupport::TestCase assert params.value?("cinco") end - test "to_query is not supported" do - assert_raises(NoMethodError) do - ActionController::Parameters.new(foo: "bar").to_param + test "to_param works like in a Hash" do + params = ActionController::Parameters.new(nested: { key: "value" }).permit! + assert_equal({ nested: { key: "value" } }.to_param, params.to_param) + + params = { root: ActionController::Parameters.new(nested: { key: "value" }).permit! } + assert_equal({ root: { nested: { key: "value" } } }.to_param, params.to_param) + + assert_raise(ActionController::UnfilteredParameters) do + ActionController::Parameters.new(nested: { key: "value" }).to_param + end + end + + test "to_query works like in a Hash" do + params = ActionController::Parameters.new(nested: { key: "value" }).permit! + assert_equal({ nested: { key: "value" } }.to_query, params.to_query) + + params = { root: ActionController::Parameters.new(nested: { key: "value" }).permit! } + assert_equal({ root: { nested: { key: "value" } } }.to_query, params.to_query) + + assert_raise(ActionController::UnfilteredParameters) do + ActionController::Parameters.new(nested: { key: "value" }).to_query end end end diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index 9e6b975fe2..e265c6c49c 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -2,7 +2,7 @@ require "abstract_unit" module TestFileUtils def file_name() File.basename(__FILE__) end - def file_path() File.expand_path(__FILE__) end + def file_path() __FILE__ end def file_data() @data ||= File.open(file_path, "rb") { |f| f.read } end end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 891ce0e905..677e2ddded 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -122,7 +122,7 @@ XML end def test_send_file - send_file(File.expand_path(__FILE__)) + send_file(__FILE__) end def redirect_to_same_controller @@ -679,6 +679,11 @@ XML assert_equal "baz", @request.filtered_parameters[:foo] end + def test_path_is_kept_after_the_request + get :test_params, params: { id: "foo" } + assert_equal "/test_case_test/test/test_params/foo", @request.path + end + def test_path_params_reset_between_request get :test_params, params: { id: "foo" } assert_equal "foo", @request.path_parameters[:id] @@ -728,20 +733,6 @@ XML assert_equal "text/html", @response.body end - def test_request_path_info_and_format_reset - get :test_format, format: "json" - assert_equal "application/json", @response.body - - get :test_uri, format: "json" - assert_equal "/test_case_test/test/test_uri.json", @response.body - - get :test_format - assert_equal "text/html", @response.body - - get :test_uri - assert_equal "/test_case_test/test/test_uri", @response.body - end - def test_request_format_kwarg_overrides_params get :test_format, format: "json", params: { format: "html" } assert_equal "application/json", @response.body @@ -789,7 +780,7 @@ XML end end - FILES_DIR = File.dirname(__FILE__) + "/../fixtures/multipart" + FILES_DIR = File.expand_path("../fixtures/multipart", __dir__) READ_BINARY = "rb:binary" READ_PLAIN = "r:binary" @@ -864,7 +855,7 @@ XML end def test_fixture_file_upload_ignores_fixture_path_given_full_path - TestCaseTest.stub :fixture_path, File.dirname(__FILE__) do + TestCaseTest.stub :fixture_path, __dir__ do uploaded_file = fixture_file_upload("#{FILES_DIR}/ruby_on_rails.jpg", "image/jpg") assert_equal File.open("#{FILES_DIR}/ruby_on_rails.jpg", READ_PLAIN).read, uploaded_file.read end diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 862dcf01c3..2afe67ed91 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -386,7 +386,7 @@ module AbstractController def test_url_action_controller_parameters add_host! - assert_raise(ArgumentError) do + assert_raise(ActionController::UnfilteredParameters) do W.new.url_for(ActionController::Parameters.new(controller: "c", action: "a", protocol: "javascript", f: "%0Aeval(name)")) end end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index 664faa31bb..e5646de82e 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -288,8 +288,7 @@ class CookiesTest < ActionController::TestCase @request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new(SALT, iterations: 2) @request.env["action_dispatch.signed_cookie_salt"] = - @request.env["action_dispatch.encrypted_cookie_salt"] = - @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT + @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] = SALT @request.host = "www.nextangle.com" end @@ -531,9 +530,7 @@ class CookiesTest < ActionController::TestCase get :set_encrypted_cookie cookies = @controller.send :cookies assert_not_equal "bar", cookies[:foo] - assert_raise TypeError do - cookies.signed[:foo] - end + assert_nil cookies.signed[:foo] assert_equal "bar", cookies.encrypted[:foo] end @@ -542,9 +539,7 @@ class CookiesTest < ActionController::TestCase get :set_encrypted_cookie cookies = @controller.send :cookies assert_not_equal "bar", cookies[:foo] - assert_raises TypeError do - cookies.signed[:foo] - end + assert_nil cookies.signed[:foo] assert_equal "bar", cookies.encrypted[:foo] end @@ -553,9 +548,7 @@ class CookiesTest < ActionController::TestCase get :set_encrypted_cookie cookies = @controller.send :cookies assert_not_equal "bar", cookies[:foo] - assert_raises ::JSON::ParserError do - cookies.signed[:foo] - end + assert_nil cookies.signed[:foo] assert_equal "bar", cookies.encrypted[:foo] end @@ -564,9 +557,7 @@ class CookiesTest < ActionController::TestCase get :set_wrapped_encrypted_cookie cookies = @controller.send :cookies assert_not_equal "wrapped: bar", cookies[:foo] - assert_raises ::JSON::ParserError do - cookies.signed[:foo] - end + assert_nil cookies.signed[:foo] assert_equal "wrapped: bar", cookies.encrypted[:foo] end @@ -577,38 +568,16 @@ class CookiesTest < ActionController::TestCase assert_equal "bar was dumped and loaded", cookies.encrypted[:foo] end - def test_encrypted_cookie_using_custom_digest - @request.env["action_dispatch.cookies_digest"] = "SHA256" - get :set_encrypted_cookie - cookies = @controller.send :cookies - assert_not_equal "bar", cookies[:foo] - assert_equal "bar", cookies.encrypted[:foo] - - sign_secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - - sha1_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: "SHA1") - sha256_verifier = ActiveSupport::MessageVerifier.new(sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer, digest: "SHA256") - - assert_raises(ActiveSupport::MessageVerifier::InvalidSignature) do - sha1_verifier.verify(cookies[:foo]) - end - - assert_nothing_raised do - sha256_verifier.verify(cookies[:foo]) - end - end - def test_encrypted_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json @request.env["action_dispatch.cookies_serializer"] = :hybrid - key_generator = @request.env["action_dispatch.key_generator"] - encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] - encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] - secret = key_generator.generate_key(encrypted_cookie_salt) - sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) + cipher = "aes-256-gcm" + salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)] + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal) - marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: Marshal).encrypt_and_sign("bar") - @request.headers["Cookie"] = "foo=#{marshal_value}" + marshal_value = encryptor.encrypt_and_sign("bar") + @request.headers["Cookie"] = "foo=#{::Rack::Utils.escape marshal_value}" get :get_encrypted_cookie @@ -616,40 +585,28 @@ class CookiesTest < ActionController::TestCase assert_not_equal "bar", cookies[:foo] assert_equal "bar", cookies.encrypted[:foo] - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) - assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) + json_encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON) + assert_not_nil @response.cookies["foo"] + assert_equal "bar", json_encryptor.decrypt_and_verify(@response.cookies["foo"]) end def test_encrypted_cookie_using_hybrid_serializer_can_read_from_json_dumped_value @request.env["action_dispatch.cookies_serializer"] = :hybrid - key_generator = @request.env["action_dispatch.key_generator"] - encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] - encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] - secret = key_generator.generate_key(encrypted_cookie_salt) - sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) - json_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON).encrypt_and_sign("bar") - @request.headers["Cookie"] = "foo=#{json_value}" - - get :get_encrypted_cookie + cipher = "aes-256-gcm" + salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)] + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON) - cookies = @controller.send :cookies - assert_not_equal "bar", cookies[:foo] - assert_equal "bar", cookies.encrypted[:foo] - - assert_nil @response.cookies["foo"] - end - - def test_compat_encrypted_cookie_using_64_byte_key - # Cookie generated with 64 bytes secret - message = ["566d4e75536d686e633246564e6b493062557079626c566d51574d30515430394c53315665564a694e4563786555744f57537454576b396a5a31566a626e52525054303d2d2d34663234333330623130623261306163363562316266323335396164666364613564643134623131"].pack("H*") - @request.headers["Cookie"] = "foo=#{message}" + json_value = encryptor.encrypt_and_sign("bar") + @request.headers["Cookie"] = "foo=#{::Rack::Utils.escape json_value}" get :get_encrypted_cookie cookies = @controller.send :cookies assert_not_equal "bar", cookies[:foo] assert_equal "bar", cookies.encrypted[:foo] + assert_nil @response.cookies["foo"] end @@ -813,10 +770,10 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) - sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret) + cipher = "aes-256-gcm" + salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)] + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal) assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end @@ -842,8 +799,6 @@ class CookiesTest < ActionController::TestCase @request.env["action_dispatch.cookies_serializer"] = :json @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee" - @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9" legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar") @@ -852,10 +807,10 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) - sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) + cipher = "aes-256-gcm" + salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)] + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON) assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end @@ -881,8 +836,6 @@ class CookiesTest < ActionController::TestCase @request.env["action_dispatch.cookies_serializer"] = :hybrid @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee" - @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9" legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar") @@ -891,10 +844,10 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) - sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) + cipher = "aes-256-gcm" + salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)] + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON) assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end @@ -920,8 +873,6 @@ class CookiesTest < ActionController::TestCase @request.env["action_dispatch.cookies_serializer"] = :hybrid @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee" - @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9" legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar") @@ -930,10 +881,10 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"]) - sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"]) - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON) + cipher = "aes-256-gcm" + salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)] + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON) assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) end @@ -959,6 +910,89 @@ class CookiesTest < ActionController::TestCase assert_nil @response.cookies["foo"] end + def test_legacy_hmac_aes_cbc_encrypted_marshal_cookie_is_upgraded_to_authenticated_encrypted_cookie + @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" + + @request.env["action_dispatch.encrypted_cookie_salt"] = + @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT + + key_generator = @request.env["action_dispatch.key_generator"] + encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] + encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] + secret = key_generator.generate_key(encrypted_cookie_salt) + sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) + marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: Marshal).encrypt_and_sign("bar") + + @request.headers["Cookie"] = "foo=#{marshal_value}" + + get :get_encrypted_cookie + + cookies = @controller.send :cookies + assert_not_equal "bar", cookies[:foo] + assert_equal "bar", cookies.encrypted[:foo] + + aead_cipher = "aes-256-gcm" + aead_salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + aead_secret = key_generator.generate_key(aead_salt)[0, ActiveSupport::MessageEncryptor.key_len(aead_cipher)] + aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: aead_cipher, serializer: Marshal) + + assert_equal "bar", aead_encryptor.decrypt_and_verify(@response.cookies["foo"]) + end + + def test_legacy_hmac_aes_cbc_encrypted_json_cookie_is_upgraded_to_authenticated_encrypted_cookie + @request.env["action_dispatch.cookies_serializer"] = :json + @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" + + @request.env["action_dispatch.encrypted_cookie_salt"] = + @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT + + key_generator = @request.env["action_dispatch.key_generator"] + encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"] + encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"] + secret = key_generator.generate_key(encrypted_cookie_salt) + sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt) + marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON).encrypt_and_sign("bar") + + @request.headers["Cookie"] = "foo=#{marshal_value}" + + get :get_encrypted_cookie + + cookies = @controller.send :cookies + assert_not_equal "bar", cookies[:foo] + assert_equal "bar", cookies.encrypted[:foo] + + aead_cipher = "aes-256-gcm" + aead_salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + aead_secret = key_generator.generate_key(aead_salt)[0, ActiveSupport::MessageEncryptor.key_len(aead_cipher)] + aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: aead_cipher, serializer: JSON) + + assert_equal "bar", aead_encryptor.decrypt_and_verify(@response.cookies["foo"]) + end + + def test_legacy_hmac_aes_cbc_encrypted_cookie_using_64_byte_key_is_upgraded_to_authenticated_encrypted_cookie + @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" + + @request.env["action_dispatch.encrypted_cookie_salt"] = + @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT + + # Cookie generated with 64 bytes secret + message = ["566d4e75536d686e633246564e6b493062557079626c566d51574d30515430394c53315665564a694e4563786555744f57537454576b396a5a31566a626e52525054303d2d2d34663234333330623130623261306163363562316266323335396164666364613564643134623131"].pack("H*") + @request.headers["Cookie"] = "foo=#{message}" + + get :get_encrypted_cookie + + cookies = @controller.send :cookies + assert_not_equal "bar", cookies[:foo] + assert_equal "bar", cookies.encrypted[:foo] + cipher = "aes-256-gcm" + + salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] + secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)] + encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal) + + assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) + end + def test_cookie_with_all_domain_option get :set_cookie_with_domain assert_response :success diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 01c5ff1429..e7e8c82974 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -21,7 +21,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest end end - FIXTURE_PATH = File.dirname(__FILE__) + "/../../fixtures/multipart" + FIXTURE_PATH = File.expand_path("../../fixtures/multipart", __dir__) def teardown TestController.last_request_parameters = nil diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb index 311b80ea0a..228135c547 100644 --- a/actionpack/test/dispatch/request/session_test.rb +++ b/actionpack/test/dispatch/request/session_test.rb @@ -54,6 +54,11 @@ module ActionDispatch assert_equal %w[rails adequate], s.keys end + def test_keys_with_deferred_loading + s = Session.create(store_with_data, req, {}) + assert_equal %w[sample_key], s.keys + end + def test_values s = Session.create(store, req, {}) s["rails"] = "ftw" @@ -61,6 +66,11 @@ module ActionDispatch assert_equal %w[ftw awesome], s.values end + def test_values_with_deferred_loading + s = Session.create(store_with_data, req, {}) + assert_equal %w[sample_value], s.values + end + def test_clear s = Session.create(store, req, {}) s["rails"] = "ftw" @@ -113,6 +123,14 @@ module ActionDispatch def delete_session(env, id, options); 123; end }.new end + + def store_with_data + Class.new { + def load_session(env); [1, { "sample_key" => "sample_value" }]; end + def session_exists?(env); true; end + def delete_session(env, id, options); 123; end + }.new + end end class SessionIntegrationTest < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb index 1169bf0cdb..6721a388c1 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -107,7 +107,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest query = [ "customers[boston][first][name]=David", "something_else=blah", - "logo=#{File.expand_path(__FILE__)}" + "logo=#{__FILE__}" ].join("&") expected = { "customers" => { @@ -118,7 +118,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest } }, "something_else" => "blah", - "logo" => File.expand_path(__FILE__), + "logo" => __FILE__, } assert_parses expected, query end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 2f9228a62d..28cbde028d 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -110,8 +110,8 @@ class RequestIP < BaseRequestTest request.remote_ip } assert_match(/IP spoofing attack/, e.message) - assert_match(/HTTP_X_FORWARDED_FOR="1.1.1.1"/, e.message) - assert_match(/HTTP_CLIENT_IP="2.2.2.2"/, e.message) + assert_match(/HTTP_X_FORWARDED_FOR="1\.1\.1\.1"/, e.message) + assert_match(/HTTP_CLIENT_IP="2\.2\.2\.2"/, e.message) end test "remote ip with spoof detection disabled" do diff --git a/actionpack/test/dispatch/routing/custom_url_helpers_test.rb b/actionpack/test/dispatch/routing/custom_url_helpers_test.rb index f85b989892..338992dda5 100644 --- a/actionpack/test/dispatch/routing/custom_url_helpers_test.rb +++ b/actionpack/test/dispatch/routing/custom_url_helpers_test.rb @@ -4,18 +4,23 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest class Linkable attr_reader :id + def self.name + super.demodulize + end + def initialize(id) @id = id end def linkable_type - self.class.name.demodulize.underscore + self.class.name.underscore end end class Category < Linkable; end class Collection < Linkable; end class Product < Linkable; end + class Manufacturer < Linkable; end class Model extend ActiveModel::Naming @@ -79,7 +84,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest get "/media/:id", to: "media#show", as: :media get "/pages/:id", to: "pages#show", as: :page - resources :categories, :collections, :products + resources :categories, :collections, :products, :manufacturers namespace :admin do get "/dashboard", to: "dashboard#index" @@ -89,6 +94,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest direct("string") { "http://www.rubyonrails.org" } direct(:helper) { basket_url } direct(:linkable) { |linkable| [:"#{linkable.linkable_type}", { id: linkable.id }] } + direct(:nested) { |linkable| route_for(:linkable, linkable) } direct(:params) { |params| params } direct(:symbol) { :basket } direct(:hash) { { controller: "basket", action: "show" } } @@ -102,6 +108,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest resolve("Article") { |article| [:post, { id: article.id }] } resolve("Basket") { |basket| [:basket] } + resolve("Manufacturer") { |manufacturer| route_for(:linkable, manufacturer) } resolve("User", anchor: "details") { |user, options| [:profile, options] } resolve("Video") { |video| [:media, { id: video.id }] } resolve(%w[Page CategoryPage ProductPage]) { |page| [:page, { id: page.id }] } @@ -119,6 +126,7 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest @category = Category.new("1") @collection = Collection.new("2") @product = Product.new("3") + @manufacturer = Manufacturer.new("apple") @basket = Basket.new @user = User.new @video = Video.new("4") @@ -136,14 +144,14 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest end def test_direct_paths - assert_equal "http://www.rubyonrails.org", website_path - assert_equal "http://www.rubyonrails.org", Routes.url_helpers.website_path + assert_equal "/", website_path + assert_equal "/", Routes.url_helpers.website_path - assert_equal "http://www.rubyonrails.org", string_path - assert_equal "http://www.rubyonrails.org", Routes.url_helpers.string_path + assert_equal "/", string_path + assert_equal "/", Routes.url_helpers.string_path - assert_equal "http://www.example.com/basket", helper_url - assert_equal "http://www.example.com/basket", Routes.url_helpers.helper_url + assert_equal "/basket", helper_path + assert_equal "/basket", Routes.url_helpers.helper_path assert_equal "/categories/1", linkable_path(@category) assert_equal "/categories/1", Routes.url_helpers.linkable_path(@category) @@ -152,10 +160,13 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest assert_equal "/products/3", linkable_path(@product) assert_equal "/products/3", Routes.url_helpers.linkable_path(@product) + assert_equal "/categories/1", nested_path(@category) + assert_equal "/categories/1", Routes.url_helpers.nested_path(@category) + assert_equal "/", params_path(@safe_params) assert_equal "/", Routes.url_helpers.params_path(@safe_params) - assert_raises(ArgumentError) { params_path(@unsafe_params) } - assert_raises(ArgumentError) { Routes.url_helpers.params_path(@unsafe_params) } + assert_raises(ActionController::UnfilteredParameters) { params_path(@unsafe_params) } + assert_raises(ActionController::UnfilteredParameters) { Routes.url_helpers.params_path(@unsafe_params) } assert_equal "/basket", symbol_path assert_equal "/basket", Routes.url_helpers.symbol_path @@ -192,10 +203,13 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest assert_equal "http://www.example.com/products/3", linkable_url(@product) assert_equal "http://www.example.com/products/3", Routes.url_helpers.linkable_url(@product) + assert_equal "http://www.example.com/categories/1", nested_url(@category) + assert_equal "http://www.example.com/categories/1", Routes.url_helpers.nested_url(@category) + assert_equal "http://www.example.com/", params_url(@safe_params) assert_equal "http://www.example.com/", Routes.url_helpers.params_url(@safe_params) - assert_raises(ArgumentError) { params_url(@unsafe_params) } - assert_raises(ArgumentError) { Routes.url_helpers.params_url(@unsafe_params) } + assert_raises(ActionController::UnfilteredParameters) { params_url(@unsafe_params) } + assert_raises(ActionController::UnfilteredParameters) { Routes.url_helpers.params_url(@unsafe_params) } assert_equal "http://www.example.com/basket", symbol_url assert_equal "http://www.example.com/basket", Routes.url_helpers.symbol_url @@ -244,6 +258,9 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest assert_equal "/pages/8", polymorphic_path(@product_page) assert_equal "/pages/8", Routes.url_helpers.polymorphic_path(@product_page) assert_equal "/pages/8", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @product_page) + + assert_equal "/manufacturers/apple", polymorphic_path(@manufacturer) + assert_equal "/manufacturers/apple", Routes.url_helpers.polymorphic_path(@manufacturer) end def test_resolve_urls @@ -277,6 +294,9 @@ class TestCustomUrlHelpers < ActionDispatch::IntegrationTest assert_equal "http://www.example.com/pages/8", polymorphic_url(@product_page) assert_equal "http://www.example.com/pages/8", Routes.url_helpers.polymorphic_url(@product_page) assert_equal "http://www.example.com/pages/8", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @product_page) + + assert_equal "http://www.example.com/manufacturers/apple", polymorphic_url(@manufacturer) + assert_equal "http://www.example.com/manufacturers/apple", Routes.url_helpers.polymorphic_url(@manufacturer) end def test_defining_direct_inside_a_scope_raises_runtime_error diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 64818e6ca1..32cd78e492 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -3633,7 +3633,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end params = ActionController::Parameters.new(id: "1") - assert_raises ArgumentError do + assert_raises ActionController::UnfilteredParameters do root_path(params) end end @@ -3706,6 +3706,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal "/bar", bar_root_path end + def test_nested_routes_under_format_resource + draw do + resources :formats do + resources :items + end + end + + get "/formats/1/items.json" + assert_equal 200, @response.status + assert_equal "items#index", @response.body + assert_equal "/formats/1/items.json", format_items_path(1, :json) + + get "/formats/1/items/2.json" + assert_equal 200, @response.status + assert_equal "items#show", @response.body + assert_equal "/formats/1/items/2.json", format_item_path(1, 2, :json) + end + private def draw(&block) @@ -4401,7 +4419,7 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest end end - test "invalid UTF-8 encoding returns a 400 Bad Request" do + test "invalid UTF-8 encoding is treated as ASCII 8BIT encode" do with_routing do |set| set.draw do get "/bar/:id", to: redirect("/foo/show/%{id}") @@ -4417,19 +4435,19 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest end get "/%E2%EF%BF%BD%A6" - assert_response :bad_request + assert_response :not_found get "/foo/%E2%EF%BF%BD%A6" - assert_response :bad_request + assert_response :not_found get "/foo/show/%E2%EF%BF%BD%A6" - assert_response :bad_request + assert_response :ok get "/bar/%E2%EF%BF%BD%A6" - assert_response :bad_request + assert_response :redirect get "/foobar/%E2%EF%BF%BD%A6" - assert_response :bad_request + assert_response :ok end end end diff --git a/actionpack/test/dispatch/system_testing/driver_test.rb b/actionpack/test/dispatch/system_testing/driver_test.rb index 8f8777b19f..814e1d707b 100644 --- a/actionpack/test/dispatch/system_testing/driver_test.rb +++ b/actionpack/test/dispatch/system_testing/driver_test.rb @@ -8,10 +8,11 @@ class DriverTest < ActiveSupport::TestCase end test "initializing the driver with a browser" do - driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :chrome, screen_size: [1400, 1400]) + driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :chrome, screen_size: [1400, 1400], options: { url: "http://example.com/wd/hub" }) assert_equal :selenium, driver.instance_variable_get(:@name) assert_equal :chrome, driver.instance_variable_get(:@browser) assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size) + assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options) end test "selenium? returns false if driver is poltergeist" do diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb index 1a9421c098..8f90e45f5f 100644 --- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb +++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb @@ -6,8 +6,28 @@ class SetDriverToRackTestTest < DrivenByRackTest end end +class OverrideSeleniumSubclassToRackTestTest < DrivenBySeleniumWithChrome + driven_by :rack_test + + test "uses rack_test" do + assert_equal :rack_test, Capybara.current_driver + end +end + class SetDriverToSeleniumTest < DrivenBySeleniumWithChrome test "uses selenium" do assert_equal :selenium, Capybara.current_driver end end + +class SetHostTest < DrivenByRackTest + test "sets default host" do + assert_equal "http://127.0.0.1", Capybara.app_host + end + + test "overrides host" do + host! "http://example.com" + + assert_equal "http://example.com", Capybara.app_host + end +end diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index 51680216e4..0074d2a314 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -13,6 +13,12 @@ module ActionDispatch assert_equal "foo", uf.original_filename end + def test_filename_is_different_object + file_str = "foo" + uf = Http::UploadedFile.new(filename: file_str, tempfile: Object.new) + assert_not_equal file_str.object_id , uf.original_filename.object_id + end + def test_filename_should_be_in_utf_8 uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new) assert_equal "UTF-8", uf.original_filename.encoding.to_s diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb index 9b88fa1f5a..dfcd423978 100644 --- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb +++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb @@ -1,3 +1,3 @@ <body> -<%= cache do %><p>ERB</p><% end %> +<%= cache("fragment") do %><p>ERB</p><% end %> </body> diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder index efdcc28e0f..6599579740 100644 --- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder +++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder @@ -1,5 +1,5 @@ xml.body do - cache do + cache("fragment") do xml.p "Builder" end end diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb index e523b74ae3..abf7017ce6 100644 --- a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb +++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb @@ -1,3 +1,3 @@ <body> -<%= cache do %><p>PHONE</p><% end %> +<%= cache("fragment") do %><p>PHONE</p><% end %> </body> diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb index fa5e6bd318..1148d83ad7 100644 --- a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb +++ b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb @@ -1,3 +1,3 @@ Hello -<%= cache do %>This bit's fragment cached<% end %> +<%= cache "fragment" do %>This bit's fragment cached<% end %> <%= 'Ciao' %> diff --git a/actionpack/test/fixtures/layouts/builder.builder b/actionpack/test/fixtures/layouts/builder.builder index 7c7d4b2dd1..c55488edd0 100644 --- a/actionpack/test/fixtures/layouts/builder.builder +++ b/actionpack/test/fixtures/layouts/builder.builder @@ -1,3 +1,3 @@ xml.wrapper do xml << yield -end
\ No newline at end of file +end diff --git a/actionpack/test/fixtures/old_content_type/render_default_for_builder.builder b/actionpack/test/fixtures/old_content_type/render_default_for_builder.builder index 598d62e2fc..15c8a7f5cf 100644 --- a/actionpack/test/fixtures/old_content_type/render_default_for_builder.builder +++ b/actionpack/test/fixtures/old_content_type/render_default_for_builder.builder @@ -1 +1 @@ -xml.p "Hello world!"
\ No newline at end of file +xml.p "Hello world!" diff --git a/actionpack/test/fixtures/respond_to/using_defaults.xml.builder b/actionpack/test/fixtures/respond_to/using_defaults.xml.builder index 598d62e2fc..15c8a7f5cf 100644 --- a/actionpack/test/fixtures/respond_to/using_defaults.xml.builder +++ b/actionpack/test/fixtures/respond_to/using_defaults.xml.builder @@ -1 +1 @@ -xml.p "Hello world!"
\ No newline at end of file +xml.p "Hello world!" diff --git a/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder b/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder index 598d62e2fc..15c8a7f5cf 100644 --- a/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder +++ b/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder @@ -1 +1 @@ -xml.p "Hello world!"
\ No newline at end of file +xml.p "Hello world!" diff --git a/actionpack/test/fixtures/test/formatted_xml_erb.builder b/actionpack/test/fixtures/test/formatted_xml_erb.builder index 14fd3549fb..f98aaa34a5 100644 --- a/actionpack/test/fixtures/test/formatted_xml_erb.builder +++ b/actionpack/test/fixtures/test/formatted_xml_erb.builder @@ -1 +1 @@ -xml.test 'failed'
\ No newline at end of file +xml.test "failed" diff --git a/actionpack/test/fixtures/test/hello_xml_world.builder b/actionpack/test/fixtures/test/hello_xml_world.builder index e7081b89fe..d16bb6b5cb 100644 --- a/actionpack/test/fixtures/test/hello_xml_world.builder +++ b/actionpack/test/fixtures/test/hello_xml_world.builder @@ -8,4 +8,4 @@ xml.html do xml.p "monks" xml.p "wiseguys" end -end
\ No newline at end of file +end diff --git a/actionpack/test/journey/gtg/transition_table_test.rb b/actionpack/test/journey/gtg/transition_table_test.rb index c7315c0338..889640fdd7 100644 --- a/actionpack/test/journey/gtg/transition_table_test.rb +++ b/actionpack/test/journey/gtg/transition_table_test.rb @@ -35,25 +35,25 @@ module ActionDispatch def test_simulate_gt sim = simulator_for ["/foo", "/bar"] - assert_match sim, "/foo" + assert_match_route sim, "/foo" end def test_simulate_gt_regexp sim = simulator_for [":foo"] - assert_match sim, "foo" + assert_match_route sim, "foo" end def test_simulate_gt_regexp_mix sim = simulator_for ["/get", "/:method/foo"] - assert_match sim, "/get" - assert_match sim, "/get/foo" + assert_match_route sim, "/get" + assert_match_route sim, "/get/foo" end def test_simulate_optional sim = simulator_for ["/foo(/bar)"] - assert_match sim, "/foo" - assert_match sim, "/foo/bar" - assert_no_match sim, "/foo/" + assert_match_route sim, "/foo" + assert_match_route sim, "/foo/bar" + assert_no_match_route sim, "/foo/" end def test_match_data @@ -65,11 +65,11 @@ module ActionDispatch sim = GTG::Simulator.new tt - match = sim.match "/get" - assert_equal [paths.first], match.memos + memos = sim.memos "/get" + assert_equal [paths.first], memos - match = sim.match "/get/foo" - assert_equal [paths.last], match.memos + memos = sim.memos "/get/foo" + assert_equal [paths.last], memos end def test_match_data_ambiguous @@ -86,8 +86,8 @@ module ActionDispatch builder = GTG::Builder.new ast sim = GTG::Simulator.new builder.transition_table - match = sim.match "/articles/new" - assert_equal [paths[1], paths[3]], match.memos + memos = sim.memos "/articles/new" + assert_equal [paths[1], paths[3]], memos end private @@ -109,6 +109,14 @@ module ActionDispatch def simulator_for(paths) GTG::Simulator.new tt(paths) end + + def assert_match_route(simulator, path) + assert simulator.memos(path), "Simulator should match #{path}." + end + + def assert_no_match_route(simulator, path) + assert_not simulator.memos(path) { nil }, "Simulator should not match #{path}." + end end end end diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb index b77bf6628a..74277a4325 100644 --- a/actionpack/test/journey/router/utils_test.rb +++ b/actionpack/test/journey/router/utils_test.rb @@ -31,6 +31,11 @@ module ActionDispatch def test_normalize_path_uppercase assert_equal "/foo%AAbar%AAbaz", Utils.normalize_path("/foo%aabar%aabaz") end + + def test_normalize_path_maintains_string_encoding + path = "/foo%AAbar%AAbaz".b + assert_equal Encoding::ASCII_8BIT, Utils.normalize_path(path).encoding + end end end end diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index b768553e7a..ff37d85ed8 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -26,6 +26,10 @@ Customer = Struct.new(:name, :id) do def persisted? id.present? end + + def cache_key + "#{name}/#{id}" + end end Post = Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) do |