diff options
Diffstat (limited to 'actionpack/lib/action_dispatch')
14 files changed, 161 insertions, 46 deletions
diff --git a/actionpack/lib/action_dispatch/http/content_disposition.rb b/actionpack/lib/action_dispatch/http/content_disposition.rb new file mode 100644 index 0000000000..58164c1522 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/content_disposition.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ActionDispatch + module Http + class ContentDisposition # :nodoc: + def self.format(disposition:, filename:) + new(disposition: disposition, filename: filename).to_s + end + + attr_reader :disposition, :filename + + def initialize(disposition:, filename:) + @disposition = disposition + @filename = filename + end + + TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/ + + def ascii_filename + 'filename="' + percent_escape(I18n.transliterate(filename), TRADITIONAL_ESCAPED_CHAR) + '"' + end + + RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/ + + def utf8_filename + "filename*=UTF-8''" + percent_escape(filename, RFC_5987_ESCAPED_CHAR) + end + + def to_s + if filename + "#{disposition}; #{ascii_filename}; #{utf8_filename}" + else + "#{disposition}" + end + end + + private + def percent_escape(string, pattern) + string.gsub(pattern) do |char| + char.bytes.map { |byte| "%%%02X" % byte }.join + end + end + end + end +end diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb index 35041fd072..855be5ce2e 100644 --- a/actionpack/lib/action_dispatch/http/content_security_policy.rb +++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb @@ -132,7 +132,7 @@ module ActionDispatch #:nodoc: worker_src: "worker-src" }.freeze - NONCE_DIRECTIVES = %w[script-src].freeze + NONCE_DIRECTIVES = %w[script-src style-src].freeze private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 7e50cb6d23..f07be831d4 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -105,7 +105,7 @@ module ActionDispatch # :nodoc: def body @str_body ||= begin - buf = "".dup + buf = +"" each { |chunk| buf << chunk } buf end @@ -224,16 +224,6 @@ module ActionDispatch # :nodoc: @status = Rack::Utils.status_code(status) end - # Sets the HTTP content type. - def content_type=(content_type) - return unless content_type - new_header_info = parse_content_type(content_type.to_s) - prev_header_info = parsed_content_type_header - charset = new_header_info.charset || prev_header_info.charset - charset ||= self.class.default_charset unless prev_header_info.mime_type - set_content_type new_header_info.mime_type, charset - end - # Sets the HTTP response's content MIME type. For example, in the controller # you could write this: # @@ -242,7 +232,17 @@ module ActionDispatch # :nodoc: # If a character set has been defined for this response (see charset=) then # the character set information will also be included in the content type # information. + def content_type=(content_type) + return unless content_type + new_header_info = parse_content_type(content_type.to_s) + prev_header_info = parsed_content_type_header + charset = new_header_info.charset || prev_header_info.charset + charset ||= self.class.default_charset unless prev_header_info.mime_type + set_content_type new_header_info.mime_type, charset + end + # Content type of response. + # It returns just MIME type and does NOT contain charset part. def content_type parsed_content_type_header.mime_type end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 35ba44005a..db6d8188d3 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -157,7 +157,7 @@ module ActionDispatch subdomain = options.fetch :subdomain, true domain = options[:domain] - host = "".dup + host = +"" if subdomain == true return _host if domain.nil? diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 0f04839d9b..52396ec901 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -50,7 +50,7 @@ module ActionDispatch unmatched_keys = (missing_keys || []) & constraints.keys missing_keys = (missing_keys || []) - unmatched_keys - message = "No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}".dup + message = +"No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}" message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty? message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty? diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index df3f79a407..3bbb187f5c 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -17,11 +17,11 @@ module ActionDispatch def self.normalize_path(path) path ||= "" encoding = path.encoding - path = "/#{path}".dup + path = +"/#{path}" path.squeeze!("/".freeze) path.sub!(%r{/+\Z}, "".freeze) path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } - path = "/".dup if path == "".freeze + path = +"/" if path == "".freeze path.force_encoding(encoding) path end @@ -32,7 +32,7 @@ module ActionDispatch ENCODE = "%%%02X".freeze US_ASCII = Encoding::US_ASCII UTF_8 = Encoding::UTF_8 - EMPTY = "".dup.force_encoding(US_ASCII).freeze + EMPTY = (+"").force_encoding(US_ASCII).freeze DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) } ALPHA = "a-zA-Z".freeze diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 077a83b112..5f5fdbc66a 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -23,7 +23,7 @@ module ActionDispatch if clean_params.empty? "None" else - PP.pp(clean_params, "".dup, 200) + PP.pp(clean_params, +"", 200) end end diff --git a/actionpack/lib/action_dispatch/middleware/debug_locks.rb b/actionpack/lib/action_dispatch/middleware/debug_locks.rb index 03760438f7..d39377f174 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_locks.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_locks.rb @@ -63,19 +63,19 @@ module ActionDispatch str = threads.map do |thread, info| if info[:exclusive] - lock_state = "Exclusive".dup + lock_state = +"Exclusive" elsif info[:sharing] > 0 - lock_state = "Sharing".dup + lock_state = +"Sharing" lock_state << " x#{info[:sharing]}" if info[:sharing] > 1 else - lock_state = "No lock".dup + lock_state = +"No lock" end if info[:waiting] lock_state << " (yielded share)" end - msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n".dup + msg = +"Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n" if info[:sleeper] msg << " Waiting in #{info[:sleeper]}" diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 190e54223e..9c9ccfa16f 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -102,7 +102,7 @@ module ActionDispatch # https://tools.ietf.org/html/rfc6797#section-6.1 def build_hsts_header(hsts) - value = "max-age=#{hsts[:expires].to_i}".dup + value = +"max-age=#{hsts[:expires].to_i}" value << "; includeSubDomains" if hsts[:subdomains] value << "; preload" if hsts[:preload] value @@ -141,7 +141,7 @@ module ActionDispatch host = @redirect[:host] || request.host port = @redirect[:port] || request.port - location = "https://#{host}".dup + location = +"https://#{host}" location << ":#{port}" if port != 80 && port != 443 location << request.fullpath location diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 07e3be4db8..8b5a39dcaf 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -50,7 +50,19 @@ module ActionDispatch private def constraint_args(constraint, request) - constraint.arity == 1 ? [request] : [request.path_parameters, request] + arity = if constraint.respond_to?(:arity) + constraint.arity + else + constraint.method(:call).arity + end + + if arity < 1 + [] + elsif arity == 1 + [request] + else + [request.path_parameters, request] + end end end @@ -308,7 +320,7 @@ module ActionDispatch def check_controller_and_action(path_params, controller, action) hash = check_part(:controller, controller, path_params, {}) do |part| translate_controller(part) { - message = "'#{part}' is not a supported controller name. This can lead to potential routing problems.".dup + message = +"'#{part}' is not a supported controller name. This can lead to potential routing problems." message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use" raise ArgumentError, message @@ -390,7 +402,7 @@ module ActionDispatch # for root cases, where the latter is the correct one. def self.normalize_path(path) path = Journey::Router::Utils.normalize_path(path) - path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^)]+\)$} + path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/(\(+[^)]+\)){1,}$} path end @@ -664,7 +676,6 @@ module ActionDispatch def define_generate_prefix(app, name) _route = @set.named_routes.get name _routes = @set - _url_helpers = @set.url_helpers script_namer = ->(options) do prefix_options = options.slice(*_route.segment_keys) @@ -676,7 +687,7 @@ module ActionDispatch # We must actually delete prefix segment keys to avoid passing them to next url_for. _route.segment_keys.each { |k| options.delete(k) } - _url_helpers.send("#{name}_path", prefix_options) + @set.url_helpers.send("#{name}_path", prefix_options) end app.routes.define_mounted_helper(name, script_namer) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 07d3a41173..da4f285f61 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -245,7 +245,7 @@ module ActionDispatch missing_keys << missing_key } constraints = Hash[@route.requirements.merge(params).sort_by { |k, v| k.to_s }] - message = "No route matches #{constraints.inspect}".dup + message = +"No route matches #{constraints.inspect}" message << ", missing required keys: #{missing_keys.sort.inspect}" raise ActionController::UrlGenerationError, message @@ -378,6 +378,8 @@ module ActionDispatch @disable_clear_and_finalize = false @finalized = false @env_key = "ROUTES_#{object_id}_SCRIPT_NAME".freeze + @url_helpers = nil + @deferred_classes = [] @set = Journey::Routes.new @router = Journey::Router.new @set @@ -433,10 +435,34 @@ module ActionDispatch end private :eval_block + def include_helpers(klass, include_path_helpers) + if @finalized + include_helpers_now klass, include_path_helpers + else + @deferred_classes << [klass, include_path_helpers] + end + end + + def include_helpers_now(klass, include_path_helpers) + namespace = klass.parents.detect { |m| m.respond_to?(:railtie_include_helpers) } + + if namespace && namespace.railtie_namespace.routes != self + namespace.railtie_include_helpers(klass, include_path_helpers) + else + klass.include(url_helpers(include_path_helpers)) + end + end + private :include_helpers_now + def finalize! return if @finalized @append.each { |blk| eval_block(blk) } @finalized = true + @url_helpers = build_url_helper_module true + @deferred_classes.each { |klass, include_path_helpers| + include_helpers klass, include_path_helpers + } + @deferred_classes.clear end def clear! @@ -465,11 +491,10 @@ module ActionDispatch return if MountedHelpers.method_defined?(name) routes = self - helpers = routes.url_helpers MountedHelpers.class_eval do define_method "_#{name}" do - RoutesProxy.new(routes, _routes_context, helpers, script_namer) + RoutesProxy.new(routes, _routes_context, routes.url_helpers, script_namer) end end @@ -480,7 +505,20 @@ module ActionDispatch RUBY end + class UnfinalizedRouteSet < StandardError + end + def url_helpers(supports_path = true) + raise UnfinalizedRouteSet, "routes have not been finalized. Please call `finalize!` or use `draw(&block)`" unless @finalized + + if supports_path + @url_helpers + else + build_url_helper_module false + end + end + + def build_url_helper_module(supports_path) routes = self Module.new do 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 d2685e0452..884fb51d18 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 @@ -65,7 +65,7 @@ module ActionDispatch end def display_image - message = "[Screenshot]: #{image_path}\n".dup + message = +"[Screenshot]: #{image_path}\n" case output_type when "artifact" diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 98b1965d22..8595ea03cf 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -79,9 +79,8 @@ module ActionDispatch end def generate_response_message(expected, actual = @response.response_code) - "Expected response to be a <#{code_with_name(expected)}>,"\ - " but was a <#{code_with_name(actual)}>" - .dup.concat(location_if_redirected).concat(response_body_if_short) + (+"Expected response to be a <#{code_with_name(expected)}>,"\ + " but was a <#{code_with_name(actual)}>").concat(location_if_redirected).concat(response_body_if_short) end def response_body_if_short diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 77cb311630..0e8712f8d9 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -9,6 +9,11 @@ module ActionDispatch module Assertions # Suite of assertions to test routes generated by \Rails and the handling of requests made to them. module RoutingAssertions + def setup # :nodoc: + @routes ||= nil + super + end + # Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash) # match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+. # @@ -133,6 +138,20 @@ module ActionDispatch assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message) end + # Provides a hook on `finalize!` so we can mutate a controller after the + # route set has been drawn. + class WithRouting < ActionDispatch::Routing::RouteSet # :nodoc: + def initialize(&block) + super() + @block = block + end + + def finalize! + super + @block.call self + end + end + # A helper to make it easier to test different route configurations. # This method temporarily replaces @routes with a new RouteSet instance. # @@ -147,16 +166,19 @@ module ActionDispatch # end # def with_routing - old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new - if defined?(@controller) && @controller - old_controller, @controller = @controller, @controller.clone - _routes = @routes - - @controller.singleton_class.include(_routes.url_helpers) - - if @controller.respond_to? :view_context_class - @controller.view_context_class = Class.new(@controller.view_context_class) do - include _routes.url_helpers + old_routes = @routes + old_controller = nil + @routes = WithRouting.new do |_routes| + if defined?(@controller) && @controller + old_controller, @controller = @controller, @controller.clone + _routes = @routes + + @controller.singleton_class.include(_routes.url_helpers) + + if @controller.respond_to? :view_context_class + @controller.view_context_class = Class.new(@controller.view_context_class) do + include _routes.url_helpers + end end end end |