actionpack 4.2.8 → 5.2.4.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 (166) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +285 -444
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller.rb +12 -5
  6. data/lib/abstract_controller/asset_paths.rb +2 -0
  7. data/lib/abstract_controller/base.rb +45 -49
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +78 -15
  10. data/lib/abstract_controller/callbacks.rb +47 -31
  11. data/lib/abstract_controller/collector.rb +8 -11
  12. data/lib/abstract_controller/error.rb +6 -0
  13. data/lib/abstract_controller/helpers.rb +25 -25
  14. data/lib/abstract_controller/logger.rb +2 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +4 -2
  16. data/lib/abstract_controller/rendering.rb +42 -41
  17. data/lib/abstract_controller/translation.rb +10 -7
  18. data/lib/abstract_controller/url_for.rb +2 -0
  19. data/lib/action_controller.rb +29 -21
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +27 -19
  23. data/lib/action_controller/caching.rb +14 -57
  24. data/lib/action_controller/form_builder.rb +50 -0
  25. data/lib/action_controller/log_subscriber.rb +10 -15
  26. data/lib/action_controller/metal.rb +98 -83
  27. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  28. data/lib/action_controller/metal/conditional_get.rb +118 -44
  29. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  30. data/lib/action_controller/metal/cookies.rb +3 -3
  31. data/lib/action_controller/metal/data_streaming.rb +27 -46
  32. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
  34. data/lib/action_controller/metal/exceptions.rb +8 -14
  35. data/lib/action_controller/metal/flash.rb +4 -3
  36. data/lib/action_controller/metal/force_ssl.rb +23 -21
  37. data/lib/action_controller/metal/head.rb +21 -19
  38. data/lib/action_controller/metal/helpers.rb +24 -14
  39. data/lib/action_controller/metal/http_authentication.rb +64 -57
  40. data/lib/action_controller/metal/implicit_render.rb +62 -8
  41. data/lib/action_controller/metal/instrumentation.rb +19 -21
  42. data/lib/action_controller/metal/live.rb +90 -106
  43. data/lib/action_controller/metal/mime_responds.rb +33 -46
  44. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  45. data/lib/action_controller/metal/params_wrapper.rb +61 -53
  46. data/lib/action_controller/metal/redirecting.rb +49 -28
  47. data/lib/action_controller/metal/renderers.rb +87 -44
  48. data/lib/action_controller/metal/rendering.rb +72 -50
  49. data/lib/action_controller/metal/request_forgery_protection.rb +203 -92
  50. data/lib/action_controller/metal/rescue.rb +9 -16
  51. data/lib/action_controller/metal/streaming.rb +12 -10
  52. data/lib/action_controller/metal/strong_parameters.rb +582 -165
  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/railtie.rb +28 -10
  56. data/lib/action_controller/railties/helpers.rb +2 -0
  57. data/lib/action_controller/renderer.rb +117 -0
  58. data/lib/action_controller/template_assertions.rb +11 -0
  59. data/lib/action_controller/test_case.rb +280 -411
  60. data/lib/action_dispatch.rb +27 -19
  61. data/lib/action_dispatch/http/cache.rb +93 -47
  62. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  63. data/lib/action_dispatch/http/filter_parameters.rb +26 -20
  64. data/lib/action_dispatch/http/filter_redirect.rb +10 -11
  65. data/lib/action_dispatch/http/headers.rb +55 -22
  66. data/lib/action_dispatch/http/mime_negotiation.rb +60 -41
  67. data/lib/action_dispatch/http/mime_type.rb +134 -121
  68. data/lib/action_dispatch/http/mime_types.rb +20 -6
  69. data/lib/action_dispatch/http/parameter_filter.rb +25 -11
  70. data/lib/action_dispatch/http/parameters.rb +98 -39
  71. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  72. data/lib/action_dispatch/http/request.rb +200 -118
  73. data/lib/action_dispatch/http/response.rb +225 -110
  74. data/lib/action_dispatch/http/upload.rb +12 -6
  75. data/lib/action_dispatch/http/url.rb +110 -28
  76. data/lib/action_dispatch/journey.rb +7 -5
  77. data/lib/action_dispatch/journey/formatter.rb +55 -32
  78. data/lib/action_dispatch/journey/gtg/builder.rb +7 -5
  79. data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
  80. data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -16
  81. data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
  82. data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
  83. data/lib/action_dispatch/journey/nfa/simulator.rb +3 -1
  84. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -48
  85. data/lib/action_dispatch/journey/nodes/node.rb +18 -6
  86. data/lib/action_dispatch/journey/parser.rb +23 -22
  87. data/lib/action_dispatch/journey/parser.y +3 -2
  88. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  89. data/lib/action_dispatch/journey/path/pattern.rb +50 -44
  90. data/lib/action_dispatch/journey/route.rb +106 -28
  91. data/lib/action_dispatch/journey/router.rb +35 -23
  92. data/lib/action_dispatch/journey/router/utils.rb +20 -11
  93. data/lib/action_dispatch/journey/routes.rb +18 -16
  94. data/lib/action_dispatch/journey/scanner.rb +18 -15
  95. data/lib/action_dispatch/journey/visitors.rb +99 -52
  96. data/lib/action_dispatch/middleware/callbacks.rb +1 -2
  97. data/lib/action_dispatch/middleware/cookies.rb +304 -193
  98. data/lib/action_dispatch/middleware/debug_exceptions.rb +152 -57
  99. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  100. data/lib/action_dispatch/middleware/exception_wrapper.rb +68 -69
  101. data/lib/action_dispatch/middleware/executor.rb +21 -0
  102. data/lib/action_dispatch/middleware/flash.rb +78 -54
  103. data/lib/action_dispatch/middleware/public_exceptions.rb +27 -25
  104. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  105. data/lib/action_dispatch/middleware/remote_ip.rb +41 -31
  106. data/lib/action_dispatch/middleware/request_id.rb +17 -9
  107. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -25
  108. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  109. data/lib/action_dispatch/middleware/session/cookie_store.rb +72 -67
  110. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  111. data/lib/action_dispatch/middleware/show_exceptions.rb +26 -22
  112. data/lib/action_dispatch/middleware/ssl.rb +114 -36
  113. data/lib/action_dispatch/middleware/stack.rb +31 -44
  114. data/lib/action_dispatch/middleware/static.rb +57 -50
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  116. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  124. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -64
  125. data/lib/action_dispatch/railtie.rb +19 -11
  126. data/lib/action_dispatch/request/session.rb +106 -59
  127. data/lib/action_dispatch/request/utils.rb +67 -24
  128. data/lib/action_dispatch/routing.rb +17 -18
  129. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  130. data/lib/action_dispatch/routing/inspector.rb +58 -67
  131. data/lib/action_dispatch/routing/mapper.rb +734 -447
  132. data/lib/action_dispatch/routing/polymorphic_routes.rb +161 -139
  133. data/lib/action_dispatch/routing/redirection.rb +36 -26
  134. data/lib/action_dispatch/routing/route_set.rb +321 -291
  135. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  136. data/lib/action_dispatch/routing/url_for.rb +65 -25
  137. data/lib/action_dispatch/system_test_case.rb +147 -0
  138. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  139. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  140. data/lib/action_dispatch/system_testing/server.rb +31 -0
  141. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  142. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  143. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  144. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  145. data/lib/action_dispatch/testing/assertions.rb +6 -4
  146. data/lib/action_dispatch/testing/assertions/response.rb +45 -20
  147. data/lib/action_dispatch/testing/assertions/routing.rb +30 -26
  148. data/lib/action_dispatch/testing/integration.rb +347 -209
  149. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  150. data/lib/action_dispatch/testing/test_process.rb +28 -22
  151. data/lib/action_dispatch/testing/test_request.rb +27 -34
  152. data/lib/action_dispatch/testing/test_response.rb +35 -7
  153. data/lib/action_pack.rb +4 -2
  154. data/lib/action_pack/gem_version.rb +5 -3
  155. data/lib/action_pack/version.rb +3 -1
  156. metadata +56 -39
  157. data/lib/action_controller/metal/hide_actions.rb +0 -40
  158. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  159. data/lib/action_controller/middleware.rb +0 -39
  160. data/lib/action_controller/model_naming.rb +0 -12
  161. data/lib/action_dispatch/journey/backwards.rb +0 -5
  162. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  163. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  164. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  165. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  166. 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 "action_dispatch/http/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 is passed to it, where the value or the key can be replaced using
13
+ # String#replace or similar method.
12
14
  #
13
15
  # env["action_dispatch.parameter_filter"] = [:password]
14
16
  # => replaces the value to all keys matching /password/i with "[FILTERED]"
@@ -16,7 +18,11 @@ 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|
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
20
26
  # v.reverse! if k =~ /secret/i
21
27
  # end
22
28
  # => reverses the value to all keys matching /secret/i
@@ -25,52 +31,52 @@ module ActionDispatch
25
31
  NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
26
32
  NULL_ENV_FILTER = 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)
38
44
  end
39
45
 
40
- # Return a hash of request.env with all sensitive data replaced.
46
+ # Returns a hash of request.env with all sensitive data replaced.
41
47
  def filtered_env
42
48
  @filtered_env ||= env_filter.filter(@env)
43
49
  end
44
50
 
45
- # Reconstructed a path with all sensitive GET parameters replaced.
51
+ # Reconstructs a path with all sensitive GET parameters replaced.
46
52
  def filtered_path
47
53
  @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
48
54
  end
49
55
 
50
- protected
56
+ private
51
57
 
52
- def parameter_filter
53
- parameter_filter_for @env.fetch("action_dispatch.parameter_filter") {
58
+ def parameter_filter # :doc:
59
+ parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
54
60
  return NULL_PARAM_FILTER
55
61
  }
56
62
  end
57
63
 
58
- def env_filter
59
- user_key = @env.fetch("action_dispatch.parameter_filter") {
64
+ def env_filter # :doc:
65
+ user_key = fetch_header("action_dispatch.parameter_filter") {
60
66
  return NULL_ENV_FILTER
61
67
  }
62
68
  parameter_filter_for(Array(user_key) + ENV_MATCH)
63
69
  end
64
70
 
65
- def parameter_filter_for(filters)
71
+ def parameter_filter_for(filters) # :doc:
66
72
  ParameterFilter.new(filters)
67
73
  end
68
74
 
69
- KV_RE = '[^&;=]+'
75
+ KV_RE = "[^&;=]+"
70
76
  PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
71
- def filtered_query_string
77
+ def filtered_query_string # :doc:
72
78
  query_string.gsub(PAIR_RE) do |_|
73
- parameter_filter.filter([[$1, $2]]).first.join("=")
79
+ parameter_filter.filter($1 => $2).first.join("=")
74
80
  end
75
81
  end
76
82
  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]".freeze # :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
@@ -15,24 +15,23 @@ module ActionDispatch
15
15
 
16
16
  private
17
17
 
18
- def location_filter
18
+ def location_filters
19
19
  if request
20
- request.env['action_dispatch.redirect_filter'] || []
20
+ request.get_header("action_dispatch.redirect_filter") || []
21
21
  else
22
22
  []
23
23
  end
24
24
  end
25
25
 
26
- def location_filter_match?(filters)
27
- filters.any? do |filter|
26
+ def location_filter_match?
27
+ location_filters.any? do |filter|
28
28
  if String === filter
29
29
  location.include?(filter)
30
30
  elsif Regexp === filter
31
- location.match(filter)
31
+ location =~ filter
32
32
  end
33
33
  end
34
34
  end
35
-
36
35
  end
37
36
  end
38
37
  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
+
120
+ # Converts an HTTP header name to an environment variable name if it is
121
+ # not contained within the headers hash.
122
+ def env_name(key)
123
+ key = key.to_s
124
+ if key =~ HTTP_HEADER
125
+ key = key.upcase.tr("-", "_")
126
+ key = "HTTP_" + key 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,4 +1,6 @@
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
@@ -6,23 +8,18 @@ module ActionDispatch
6
8
  extend ActiveSupport::Concern
7
9
 
8
10
  included do
9
- mattr_accessor :ignore_accept_header
10
- self.ignore_accept_header = false
11
+ mattr_accessor :ignore_accept_header, default: false
11
12
  end
12
13
 
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.
14
+ # The MIME type of the HTTP request, such as Mime[:xml].
19
15
  def content_mime_type
20
- @env["action_dispatch.request.content_type"] ||= begin
21
- if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
16
+ fetch_header("action_dispatch.request.content_type") do |k|
17
+ v = if get_header("CONTENT_TYPE") =~ /^([^,\;]*)/
22
18
  Mime::Type.lookup($1.strip.downcase)
23
19
  else
24
20
  nil
25
21
  end
22
+ set_header k, v
26
23
  end
27
24
  end
28
25
 
@@ -30,63 +27,80 @@ module ActionDispatch
30
27
  content_mime_type && content_mime_type.to_s
31
28
  end
32
29
 
30
+ def has_content_type? # :nodoc:
31
+ get_header "CONTENT_TYPE"
32
+ end
33
+
33
34
  # Returns the accepted MIME type for the request.
34
35
  def accepts
35
- @env["action_dispatch.request.accepts"] ||= begin
36
- header = @env['HTTP_ACCEPT'].to_s.strip
36
+ fetch_header("action_dispatch.request.accepts") do |k|
37
+ header = get_header("HTTP_ACCEPT").to_s.strip
37
38
 
38
- if header.empty?
39
+ v = if header.empty?
39
40
  [content_mime_type]
40
41
  else
41
42
  Mime::Type.parse(header)
42
43
  end
44
+ set_header k, v
43
45
  end
44
46
  end
45
47
 
46
48
  # Returns the MIME type for the \format used in the request.
47
49
  #
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
50
+ # GET /posts/5.xml | request.format => Mime[:xml]
51
+ # GET /posts/5.xhtml | request.format => Mime[:html]
52
+ # GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
51
53
  #
52
54
  def format(view_path = [])
53
55
  formats.first || Mime::NullType.instance
54
56
  end
55
57
 
56
58
  def formats
57
- @env["action_dispatch.request.formats"] ||= begin
59
+ fetch_header("action_dispatch.request.formats") do |k|
58
60
  params_readable = begin
59
61
  parameters[:format]
60
62
  rescue ActionController::BadRequest
61
63
  false
62
64
  end
63
65
 
64
- if params_readable
66
+ v = if params_readable
65
67
  Array(Mime[parameters[:format]])
66
68
  elsif use_accept_header && valid_accept_header
67
69
  accepts
70
+ elsif extension_format = format_from_path_extension
71
+ [extension_format]
68
72
  elsif xhr?
69
- [Mime::JS]
73
+ [Mime[:js]]
70
74
  else
71
- [Mime::HTML]
75
+ [Mime[:html]]
72
76
  end
77
+
78
+ v = v.select do |format|
79
+ format.symbol || format.ref == "*/*"
80
+ end
81
+
82
+ set_header k, v
73
83
  end
74
84
  end
75
85
 
76
86
  # Sets the \variant for template.
77
87
  def variant=(variant)
78
- if variant.is_a?(Symbol)
79
- @variant = [variant]
80
- elsif variant.nil? || variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) }
81
- @variant = variant
88
+ variant = Array(variant)
89
+
90
+ if variant.all? { |v| v.is_a?(Symbol) }
91
+ @variant = ActiveSupport::ArrayInquirer.new(variant)
82
92
  else
83
- raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols, not a #{variant.class}. " \
93
+ raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols. " \
84
94
  "For security reasons, never directly set the variant to a user-provided value, " \
85
95
  "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
86
96
  "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
87
97
  end
88
98
  end
89
99
 
100
+ def variant
101
+ @variant ||= ActiveSupport::ArrayInquirer.new
102
+ end
103
+
90
104
  # Sets the \format by string extension, which can be used to force custom formats
91
105
  # that are not controlled by the extension.
92
106
  #
@@ -100,7 +114,7 @@ module ActionDispatch
100
114
  # end
101
115
  def format=(extension)
102
116
  parameters[:format] = extension.to_s
103
- @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])]
117
+ set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])]
104
118
  end
105
119
 
106
120
  # Sets the \formats by string extensions. This differs from #format= by allowing you
@@ -119,14 +133,12 @@ module ActionDispatch
119
133
  # end
120
134
  def formats=(extensions)
121
135
  parameters[:format] = extensions.first.to_s
122
- @env["action_dispatch.request.formats"] = extensions.collect do |extension|
136
+ set_header "action_dispatch.request.formats", extensions.collect { |extension|
123
137
  Mime::Type.lookup_by_extension(extension)
124
- end
138
+ }
125
139
  end
126
140
 
127
- # Receives an array of mimes and return the first user sent mime that
128
- # matches the order array.
129
- #
141
+ # Returns the first MIME type that matches the provided array of MIME types.
130
142
  def negotiate_mime(order)
131
143
  formats.each do |priority|
132
144
  if priority == Mime::ALL
@@ -139,18 +151,25 @@ module ActionDispatch
139
151
  order.include?(Mime::ALL) ? format : nil
140
152
  end
141
153
 
142
- protected
154
+ private
143
155
 
144
- BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
156
+ BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
145
157
 
146
- def valid_accept_header
147
- (xhr? && (accept.present? || content_mime_type)) ||
148
- (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
149
- end
158
+ def valid_accept_header # :doc:
159
+ (xhr? && (accept.present? || content_mime_type)) ||
160
+ (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
161
+ end
150
162
 
151
- def use_accept_header
152
- !self.class.ignore_accept_header
153
- end
163
+ def use_accept_header # :doc:
164
+ !self.class.ignore_accept_header
165
+ end
166
+
167
+ def format_from_path_extension # :doc:
168
+ path = get_header("action_dispatch.original_path") || get_header("PATH_INFO")
169
+ if match = path && path.match(/\.(\w+)\z/)
170
+ Mime[match.captures.first]
171
+ end
172
+ end
154
173
  end
155
174
  end
156
175
  end