actionpack 4.2.11.1 → 6.1.3.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +291 -489
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -9
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +81 -51
  7. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +64 -17
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +61 -33
  10. data/lib/abstract_controller/collector.rb +9 -13
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +115 -99
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +21 -3
  15. data/lib/abstract_controller/rendering.rb +48 -47
  16. data/lib/abstract_controller/translation.rb +17 -8
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/abstract_controller.rb +13 -5
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +150 -0
  21. data/lib/action_controller/base.rb +29 -24
  22. data/lib/action_controller/caching.rb +12 -57
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +17 -19
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +134 -46
  27. data/lib/action_controller/metal/content_security_policy.rb +51 -0
  28. data/lib/action_controller/metal/cookies.rb +6 -4
  29. data/lib/action_controller/metal/data_streaming.rb +30 -50
  30. data/lib/action_controller/metal/default_headers.rb +17 -0
  31. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  32. data/lib/action_controller/metal/etag_with_template_digest.rb +21 -16
  33. data/lib/action_controller/metal/exceptions.rb +63 -15
  34. data/lib/action_controller/metal/flash.rb +9 -8
  35. data/lib/action_controller/metal/head.rb +26 -21
  36. data/lib/action_controller/metal/helpers.rb +37 -18
  37. data/lib/action_controller/metal/http_authentication.rb +81 -73
  38. data/lib/action_controller/metal/implicit_render.rb +53 -9
  39. data/lib/action_controller/metal/instrumentation.rb +32 -35
  40. data/lib/action_controller/metal/live.rb +102 -120
  41. data/lib/action_controller/metal/logging.rb +20 -0
  42. data/lib/action_controller/metal/mime_responds.rb +49 -47
  43. data/lib/action_controller/metal/parameter_encoding.rb +82 -0
  44. data/lib/action_controller/metal/params_wrapper.rb +83 -66
  45. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  46. data/lib/action_controller/metal/redirecting.rb +53 -32
  47. data/lib/action_controller/metal/renderers.rb +87 -44
  48. data/lib/action_controller/metal/rendering.rb +77 -50
  49. data/lib/action_controller/metal/request_forgery_protection.rb +267 -103
  50. data/lib/action_controller/metal/rescue.rb +10 -17
  51. data/lib/action_controller/metal/streaming.rb +12 -11
  52. data/lib/action_controller/metal/strong_parameters.rb +714 -186
  53. data/lib/action_controller/metal/testing.rb +2 -17
  54. data/lib/action_controller/metal/url_for.rb +19 -10
  55. data/lib/action_controller/metal.rb +104 -87
  56. data/lib/action_controller/railtie.rb +28 -10
  57. data/lib/action_controller/railties/helpers.rb +3 -1
  58. data/lib/action_controller/renderer.rb +141 -0
  59. data/lib/action_controller/template_assertions.rb +11 -0
  60. data/lib/action_controller/test_case.rb +296 -422
  61. data/lib/action_controller.rb +34 -23
  62. data/lib/action_dispatch/http/cache.rb +107 -56
  63. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  64. data/lib/action_dispatch/http/content_security_policy.rb +286 -0
  65. data/lib/action_dispatch/http/filter_parameters.rb +32 -25
  66. data/lib/action_dispatch/http/filter_redirect.rb +10 -12
  67. data/lib/action_dispatch/http/headers.rb +55 -22
  68. data/lib/action_dispatch/http/mime_negotiation.rb +79 -51
  69. data/lib/action_dispatch/http/mime_type.rb +153 -121
  70. data/lib/action_dispatch/http/mime_types.rb +20 -6
  71. data/lib/action_dispatch/http/parameters.rb +90 -40
  72. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  73. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  74. data/lib/action_dispatch/http/request.rb +226 -121
  75. data/lib/action_dispatch/http/response.rb +248 -113
  76. data/lib/action_dispatch/http/upload.rb +21 -7
  77. data/lib/action_dispatch/http/url.rb +182 -100
  78. data/lib/action_dispatch/journey/formatter.rb +90 -43
  79. data/lib/action_dispatch/journey/gtg/builder.rb +28 -41
  80. data/lib/action_dispatch/journey/gtg/simulator.rb +11 -16
  81. data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -21
  82. data/lib/action_dispatch/journey/nfa/dot.rb +3 -14
  83. data/lib/action_dispatch/journey/nodes/node.rb +29 -15
  84. data/lib/action_dispatch/journey/parser.rb +17 -16
  85. data/lib/action_dispatch/journey/parser.y +4 -3
  86. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  87. data/lib/action_dispatch/journey/path/pattern.rb +58 -54
  88. data/lib/action_dispatch/journey/route.rb +100 -32
  89. data/lib/action_dispatch/journey/router/utils.rb +29 -18
  90. data/lib/action_dispatch/journey/router.rb +55 -51
  91. data/lib/action_dispatch/journey/routes.rb +17 -17
  92. data/lib/action_dispatch/journey/scanner.rb +26 -17
  93. data/lib/action_dispatch/journey/visitors.rb +98 -54
  94. data/lib/action_dispatch/journey.rb +5 -5
  95. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  96. data/lib/action_dispatch/middleware/callbacks.rb +3 -6
  97. data/lib/action_dispatch/middleware/cookies.rb +347 -217
  98. data/lib/action_dispatch/middleware/debug_exceptions.rb +135 -63
  99. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  100. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  101. data/lib/action_dispatch/middleware/exception_wrapper.rb +115 -71
  102. data/lib/action_dispatch/middleware/executor.rb +21 -0
  103. data/lib/action_dispatch/middleware/flash.rb +78 -54
  104. data/lib/action_dispatch/middleware/host_authorization.rb +130 -0
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +32 -27
  106. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  107. data/lib/action_dispatch/middleware/remote_ip.rb +53 -45
  108. data/lib/action_dispatch/middleware/request_id.rb +17 -10
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -26
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +74 -75
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +28 -23
  114. data/lib/action_dispatch/middleware/ssl.rb +118 -35
  115. data/lib/action_dispatch/middleware/stack.rb +82 -41
  116. data/lib/action_dispatch/middleware/static.rb +156 -89
  117. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -14
  121. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +4 -2
  123. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  125. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +105 -8
  132. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  135. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  136. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  137. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  138. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  139. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  140. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +87 -64
  141. data/lib/action_dispatch/railtie.rb +27 -13
  142. data/lib/action_dispatch/request/session.rb +109 -61
  143. data/lib/action_dispatch/request/utils.rb +90 -23
  144. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  145. data/lib/action_dispatch/routing/inspector.rb +141 -102
  146. data/lib/action_dispatch/routing/mapper.rb +811 -473
  147. data/lib/action_dispatch/routing/polymorphic_routes.rb +167 -143
  148. data/lib/action_dispatch/routing/redirection.rb +37 -27
  149. data/lib/action_dispatch/routing/route_set.rb +363 -331
  150. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  151. data/lib/action_dispatch/routing/url_for.rb +66 -26
  152. data/lib/action_dispatch/routing.rb +36 -36
  153. data/lib/action_dispatch/system_test_case.rb +190 -0
  154. data/lib/action_dispatch/system_testing/browser.rb +86 -0
  155. data/lib/action_dispatch/system_testing/driver.rb +67 -0
  156. data/lib/action_dispatch/system_testing/server.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +138 -0
  158. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +29 -0
  159. data/lib/action_dispatch/testing/assertion_response.rb +46 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +44 -22
  161. data/lib/action_dispatch/testing/assertions/routing.rb +47 -31
  162. data/lib/action_dispatch/testing/assertions.rb +6 -4
  163. data/lib/action_dispatch/testing/integration.rb +391 -220
  164. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  165. data/lib/action_dispatch/testing/test_process.rb +53 -22
  166. data/lib/action_dispatch/testing/test_request.rb +27 -34
  167. data/lib/action_dispatch/testing/test_response.rb +11 -11
  168. data/lib/action_dispatch.rb +35 -21
  169. data/lib/action_pack/gem_version.rb +6 -4
  170. data/lib/action_pack/version.rb +3 -1
  171. data/lib/action_pack.rb +4 -2
  172. metadata +78 -48
  173. data/lib/action_controller/metal/force_ssl.rb +0 -97
  174. data/lib/action_controller/metal/hide_actions.rb +0 -40
  175. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  176. data/lib/action_controller/middleware.rb +0 -39
  177. data/lib/action_controller/model_naming.rb +0 -12
  178. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  179. data/lib/action_dispatch/journey/backwards.rb +0 -5
  180. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  181. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  182. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  183. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  184. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  185. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  186. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  187. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,14 +1,16 @@
1
- require 'active_support/core_ext/hash/keys'
2
- require 'active_support/core_ext/object/duplicable'
3
- require 'action_dispatch/http/parameter_filter'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/parameter_filter"
4
4
 
5
5
  module ActionDispatch
6
6
  module Http
7
7
  # Allows you to specify sensitive parameters which will be replaced from
8
8
  # the request log by looking in the query string of the request and all
9
- # sub-hashes of the params hash to filter. If a block is given, each key and
10
- # value of the params hash and all sub-hashes is passed to it, the value
11
- # or key can be replaced using String#replace or similar method.
9
+ # sub-hashes of the params hash to filter. Filtering only certain sub-keys
10
+ # from a hash is possible by using the dot notation: 'credit_card.number'.
11
+ # If a block is given, each key and value of the params hash and all
12
+ # sub-hashes are passed to it, where the value or the key can be replaced using
13
+ # String#replace or similar methods.
12
14
  #
13
15
  # env["action_dispatch.parameter_filter"] = [:password]
14
16
  # => replaces the value to all keys matching /password/i with "[FILTERED]"
@@ -16,61 +18,66 @@ module ActionDispatch
16
18
  # env["action_dispatch.parameter_filter"] = [:foo, "bar"]
17
19
  # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
18
20
  #
19
- # env["action_dispatch.parameter_filter"] = lambda do |k,v|
20
- # v.reverse! if k =~ /secret/i
21
+ # env["action_dispatch.parameter_filter"] = [ "credit_card.code" ]
22
+ # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
23
+ # change { file: { code: "xxxx"} }
24
+ #
25
+ # env["action_dispatch.parameter_filter"] = -> (k, v) do
26
+ # v.reverse! if k.match?(/secret/i)
21
27
  # end
22
28
  # => reverses the value to all keys matching /secret/i
23
29
  module FilterParameters
24
30
  ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
25
- NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
26
- NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
31
+ NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
32
+ NULL_ENV_FILTER = ActiveSupport::ParameterFilter.new ENV_MATCH # :nodoc:
27
33
 
28
- def initialize(env)
34
+ def initialize
29
35
  super
30
36
  @filtered_parameters = nil
31
37
  @filtered_env = nil
32
38
  @filtered_path = nil
33
39
  end
34
40
 
35
- # Return a hash of parameters with all sensitive data replaced.
41
+ # Returns a hash of parameters with all sensitive data replaced.
36
42
  def filtered_parameters
37
43
  @filtered_parameters ||= parameter_filter.filter(parameters)
44
+ rescue ActionDispatch::Http::Parameters::ParseError
45
+ @filtered_parameters = {}
38
46
  end
39
47
 
40
- # Return a hash of request.env with all sensitive data replaced.
48
+ # Returns a hash of request.env with all sensitive data replaced.
41
49
  def filtered_env
42
50
  @filtered_env ||= env_filter.filter(@env)
43
51
  end
44
52
 
45
- # Reconstructed a path with all sensitive GET parameters replaced.
53
+ # Reconstructs a path with all sensitive GET parameters replaced.
46
54
  def filtered_path
47
55
  @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
48
56
  end
49
57
 
50
- protected
51
-
52
- def parameter_filter
53
- parameter_filter_for @env.fetch("action_dispatch.parameter_filter") {
58
+ private
59
+ def parameter_filter # :doc:
60
+ parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
54
61
  return NULL_PARAM_FILTER
55
62
  }
56
63
  end
57
64
 
58
- def env_filter
59
- user_key = @env.fetch("action_dispatch.parameter_filter") {
65
+ def env_filter # :doc:
66
+ user_key = fetch_header("action_dispatch.parameter_filter") {
60
67
  return NULL_ENV_FILTER
61
68
  }
62
69
  parameter_filter_for(Array(user_key) + ENV_MATCH)
63
70
  end
64
71
 
65
- def parameter_filter_for(filters)
66
- ParameterFilter.new(filters)
72
+ def parameter_filter_for(filters) # :doc:
73
+ ActiveSupport::ParameterFilter.new(filters)
67
74
  end
68
75
 
69
- KV_RE = '[^&;=]+'
76
+ KV_RE = "[^&;=]+"
70
77
  PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
71
- def filtered_query_string
78
+ def filtered_query_string # :doc:
72
79
  query_string.gsub(PAIR_RE) do |_|
73
- parameter_filter.filter([[$1, $2]]).first.join("=")
80
+ parameter_filter.filter($1 => $2).first.join("=")
74
81
  end
75
82
  end
76
83
  end
@@ -1,12 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionDispatch
2
4
  module Http
3
5
  module FilterRedirect
6
+ FILTERED = "[FILTERED]" # :nodoc:
4
7
 
5
- FILTERED = '[FILTERED]'.freeze # :nodoc:
6
-
7
- def filtered_location
8
- filters = location_filter
9
- if !filters.empty? && location_filter_match?(filters)
8
+ def filtered_location # :nodoc:
9
+ if location_filter_match?
10
10
  FILTERED
11
11
  else
12
12
  location
@@ -14,25 +14,23 @@ module ActionDispatch
14
14
  end
15
15
 
16
16
  private
17
-
18
- def location_filter
17
+ def location_filters
19
18
  if request
20
- request.env['action_dispatch.redirect_filter'] || []
19
+ request.get_header("action_dispatch.redirect_filter") || []
21
20
  else
22
21
  []
23
22
  end
24
23
  end
25
24
 
26
- def location_filter_match?(filters)
27
- filters.any? do |filter|
25
+ def location_filter_match?
26
+ location_filters.any? do |filter|
28
27
  if String === filter
29
28
  location.include?(filter)
30
29
  elsif Regexp === filter
31
- location.match(filter)
30
+ location.match?(filter)
32
31
  end
33
32
  end
34
33
  end
35
-
36
34
  end
37
35
  end
38
36
  end
@@ -1,10 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionDispatch
2
4
  module Http
3
5
  # Provides access to the request's HTTP headers from the environment.
4
6
  #
5
- # env = { "CONTENT_TYPE" => "text/plain" }
6
- # headers = ActionDispatch::Http::Headers.new(env)
7
+ # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
8
+ # headers = ActionDispatch::Http::Headers.from_hash(env)
7
9
  # headers["Content-Type"] # => "text/plain"
10
+ # headers["User-Agent"] # => "curl/7.43.0"
11
+ #
12
+ # Also note that when headers are mapped to CGI-like variables by the Rack
13
+ # server, both dashes and underscores are converted to underscores. This
14
+ # ambiguity cannot be resolved at this stage anymore. Both underscores and
15
+ # dashes have to be interpreted as if they were originally sent as dashes.
16
+ #
17
+ # # GET / HTTP/1.1
18
+ # # ...
19
+ # # User-Agent: curl/7.43.0
20
+ # # X_Custom_Header: token
21
+ #
22
+ # headers["X_Custom_Header"] # => nil
23
+ # headers["X-Custom-Header"] # => "token"
8
24
  class Headers
9
25
  CGI_VARIABLES = Set.new(%W[
10
26
  AUTH_TYPE
@@ -30,27 +46,37 @@ module ActionDispatch
30
46
  HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
31
47
 
32
48
  include Enumerable
33
- attr_reader :env
34
49
 
35
- def initialize(env = {}) # :nodoc:
36
- @env = env
50
+ def self.from_hash(hash)
51
+ new ActionDispatch::Request.new hash
52
+ end
53
+
54
+ def initialize(request) # :nodoc:
55
+ @req = request
37
56
  end
38
57
 
39
58
  # Returns the value for the given key mapped to @env.
40
59
  def [](key)
41
- @env[env_name(key)]
60
+ @req.get_header env_name(key)
42
61
  end
43
62
 
44
63
  # Sets the given value for the key mapped to @env.
45
64
  def []=(key, value)
46
- @env[env_name(key)] = value
65
+ @req.set_header env_name(key), value
66
+ end
67
+
68
+ # Add a value to a multivalued header like Vary or Accept-Encoding.
69
+ def add(key, value)
70
+ @req.add_header env_name(key), value
47
71
  end
48
72
 
49
73
  def key?(key)
50
- @env.key? env_name(key)
74
+ @req.has_header? env_name(key)
51
75
  end
52
76
  alias :include? :key?
53
77
 
78
+ DEFAULT = Object.new # :nodoc:
79
+
54
80
  # Returns the value for the given key mapped to @env.
55
81
  #
56
82
  # If the key is not found and an optional code block is not provided,
@@ -58,18 +84,22 @@ module ActionDispatch
58
84
  #
59
85
  # If the code block is provided, then it will be run and
60
86
  # its result returned.
61
- def fetch(key, *args, &block)
62
- @env.fetch env_name(key), *args, &block
87
+ def fetch(key, default = DEFAULT)
88
+ @req.fetch_header(env_name(key)) do
89
+ return default unless default == DEFAULT
90
+ return yield if block_given?
91
+ raise KeyError, key
92
+ end
63
93
  end
64
94
 
65
95
  def each(&block)
66
- @env.each(&block)
96
+ @req.each_header(&block)
67
97
  end
68
98
 
69
99
  # Returns a new Http::Headers instance containing the contents of
70
100
  # <tt>headers_or_env</tt> and the original instance.
71
101
  def merge(headers_or_env)
72
- headers = Http::Headers.new(env.dup)
102
+ headers = @req.dup.headers
73
103
  headers.merge!(headers_or_env)
74
104
  headers
75
105
  end
@@ -79,21 +109,24 @@ module ActionDispatch
79
109
  # <tt>headers_or_env</tt>.
80
110
  def merge!(headers_or_env)
81
111
  headers_or_env.each do |key, value|
82
- self[env_name(key)] = value
112
+ @req.set_header env_name(key), value
83
113
  end
84
114
  end
85
115
 
116
+ def env; @req.env.dup; end
117
+
86
118
  private
87
- # Converts a HTTP header name to an environment variable name if it is
88
- # not contained within the headers hash.
89
- def env_name(key)
90
- key = key.to_s
91
- if key =~ HTTP_HEADER
92
- key = key.upcase.tr('-', '_')
93
- key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
119
+ # Converts an HTTP header name to an environment variable name if it is
120
+ # not contained within the headers hash.
121
+ def env_name(key)
122
+ key = key.to_s
123
+ if HTTP_HEADER.match?(key)
124
+ key = key.upcase
125
+ key.tr!("-", "_")
126
+ key.prepend("HTTP_") unless CGI_VARIABLES.include?(key)
127
+ end
128
+ key
94
129
  end
95
- key
96
- end
97
130
  end
98
131
  end
99
132
  end
@@ -1,28 +1,34 @@
1
- require 'active_support/core_ext/module/attribute_accessors'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/attribute_accessors"
2
4
 
3
5
  module ActionDispatch
4
6
  module Http
5
7
  module MimeNegotiation
6
8
  extend ActiveSupport::Concern
7
9
 
10
+ class InvalidType < ::Mime::Type::InvalidMimeType; end
11
+
12
+ RESCUABLE_MIME_FORMAT_ERRORS = [
13
+ ActionController::BadRequest,
14
+ ActionDispatch::Http::Parameters::ParseError,
15
+ ]
16
+
8
17
  included do
9
- mattr_accessor :ignore_accept_header
10
- self.ignore_accept_header = false
18
+ mattr_accessor :ignore_accept_header, default: false
11
19
  end
12
20
 
13
- attr_reader :variant
14
-
15
- # The MIME type of the HTTP request, such as Mime::XML.
16
- #
17
- # For backward compatibility, the post \format is extracted from the
18
- # X-Post-Data-Format HTTP header if present.
21
+ # The MIME type of the HTTP request, such as Mime[:xml].
19
22
  def content_mime_type
20
- @env["action_dispatch.request.content_type"] ||= begin
21
- if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
23
+ fetch_header("action_dispatch.request.content_type") do |k|
24
+ v = if get_header("CONTENT_TYPE") =~ /^([^,\;]*)/
22
25
  Mime::Type.lookup($1.strip.downcase)
23
26
  else
24
27
  nil
25
28
  end
29
+ set_header k, v
30
+ rescue ::Mime::Type::InvalidMimeType => e
31
+ raise InvalidType, e.message
26
32
  end
27
33
  end
28
34
 
@@ -30,67 +36,73 @@ module ActionDispatch
30
36
  content_mime_type && content_mime_type.to_s
31
37
  end
32
38
 
39
+ def has_content_type? # :nodoc:
40
+ get_header "CONTENT_TYPE"
41
+ end
42
+
33
43
  # Returns the accepted MIME type for the request.
34
44
  def accepts
35
- @env["action_dispatch.request.accepts"] ||= begin
36
- header = @env['HTTP_ACCEPT'].to_s.strip
45
+ fetch_header("action_dispatch.request.accepts") do |k|
46
+ header = get_header("HTTP_ACCEPT").to_s.strip
37
47
 
38
- if header.empty?
48
+ v = if header.empty?
39
49
  [content_mime_type]
40
50
  else
41
51
  Mime::Type.parse(header)
42
52
  end
53
+ set_header k, v
54
+ rescue ::Mime::Type::InvalidMimeType => e
55
+ raise InvalidType, e.message
43
56
  end
44
57
  end
45
58
 
46
59
  # Returns the MIME type for the \format used in the request.
47
60
  #
48
- # GET /posts/5.xml | request.format => Mime::XML
49
- # GET /posts/5.xhtml | request.format => Mime::HTML
50
- # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first
61
+ # GET /posts/5.xml | request.format => Mime[:xml]
62
+ # GET /posts/5.xhtml | request.format => Mime[:html]
63
+ # GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
51
64
  #
52
65
  def format(view_path = [])
53
66
  formats.first || Mime::NullType.instance
54
67
  end
55
68
 
56
69
  def formats
57
- @env["action_dispatch.request.formats"] ||= begin
58
- params_readable = begin
59
- parameters[:format]
60
- rescue ActionController::BadRequest
61
- false
62
- end
63
-
64
- v = if params_readable
70
+ fetch_header("action_dispatch.request.formats") do |k|
71
+ v = if params_readable?
65
72
  Array(Mime[parameters[:format]])
66
73
  elsif use_accept_header && valid_accept_header
67
74
  accepts
75
+ elsif extension_format = format_from_path_extension
76
+ [extension_format]
68
77
  elsif xhr?
69
- [Mime::JS]
78
+ [Mime[:js]]
70
79
  else
71
- [Mime::HTML]
80
+ [Mime[:html]]
72
81
  end
73
82
 
74
- v.select do |format|
83
+ v = v.select do |format|
75
84
  format.symbol || format.ref == "*/*"
76
85
  end
86
+
87
+ set_header k, v
77
88
  end
78
89
  end
79
90
 
80
91
  # Sets the \variant for template.
81
92
  def variant=(variant)
82
- if variant.is_a?(Symbol)
83
- @variant = [variant]
84
- elsif variant.nil? || variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) }
85
- @variant = variant
93
+ variant = Array(variant)
94
+
95
+ if variant.all? { |v| v.is_a?(Symbol) }
96
+ @variant = ActiveSupport::ArrayInquirer.new(variant)
86
97
  else
87
- raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols, not a #{variant.class}. " \
88
- "For security reasons, never directly set the variant to a user-provided value, " \
89
- "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
90
- "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
98
+ raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols."
91
99
  end
92
100
  end
93
101
 
102
+ def variant
103
+ @variant ||= ActiveSupport::ArrayInquirer.new
104
+ end
105
+
94
106
  # Sets the \format by string extension, which can be used to force custom formats
95
107
  # that are not controlled by the extension.
96
108
  #
@@ -104,7 +116,7 @@ module ActionDispatch
104
116
  # end
105
117
  def format=(extension)
106
118
  parameters[:format] = extension.to_s
107
- @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
119
+ set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])]
108
120
  end
109
121
 
110
122
  # Sets the \formats by string extensions. This differs from #format= by allowing you
@@ -123,14 +135,12 @@ module ActionDispatch
123
135
  # end
124
136
  def formats=(extensions)
125
137
  parameters[:format] = extensions.first.to_s
126
- @env["action_dispatch.request.formats"] = extensions.collect do |extension|
138
+ set_header "action_dispatch.request.formats", extensions.collect { |extension|
127
139
  Mime::Type.lookup_by_extension(extension)
128
- end
140
+ }
129
141
  end
130
142
 
131
- # Receives an array of mimes and return the first user sent mime that
132
- # matches the order array.
133
- #
143
+ # Returns the first MIME type that matches the provided array of MIME types.
134
144
  def negotiate_mime(order)
135
145
  formats.each do |priority|
136
146
  if priority == Mime::ALL
@@ -143,18 +153,36 @@ module ActionDispatch
143
153
  order.include?(Mime::ALL) ? format : nil
144
154
  end
145
155
 
146
- protected
156
+ def should_apply_vary_header?
157
+ !params_readable? && use_accept_header && valid_accept_header
158
+ end
147
159
 
148
- BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
160
+ private
161
+ # We use normal content negotiation unless you include */* in your list,
162
+ # in which case we assume you're a browser and send HTML.
163
+ BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
149
164
 
150
- def valid_accept_header
151
- (xhr? && (accept.present? || content_mime_type)) ||
152
- (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
153
- end
165
+ def params_readable? # :doc:
166
+ parameters[:format]
167
+ rescue *RESCUABLE_MIME_FORMAT_ERRORS
168
+ false
169
+ end
154
170
 
155
- def use_accept_header
156
- !self.class.ignore_accept_header
157
- end
171
+ def valid_accept_header # :doc:
172
+ (xhr? && (accept.present? || content_mime_type)) ||
173
+ (accept.present? && !accept.match?(BROWSER_LIKE_ACCEPTS))
174
+ end
175
+
176
+ def use_accept_header # :doc:
177
+ !self.class.ignore_accept_header
178
+ end
179
+
180
+ def format_from_path_extension # :doc:
181
+ path = get_header("action_dispatch.original_path") || get_header("PATH_INFO")
182
+ if match = path && path.match(/\.(\w+)\z/)
183
+ Mime[match.captures.first]
184
+ end
185
+ end
158
186
  end
159
187
  end
160
188
  end