actionpack 5.2.4.4 → 6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -322
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller.rb +1 -0
  6. data/lib/abstract_controller/base.rb +38 -4
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/caching/fragments.rb +6 -22
  9. data/lib/abstract_controller/callbacks.rb +14 -2
  10. data/lib/abstract_controller/collector.rb +1 -2
  11. data/lib/abstract_controller/helpers.rb +106 -90
  12. data/lib/abstract_controller/railties/routes_helpers.rb +1 -1
  13. data/lib/abstract_controller/rendering.rb +9 -9
  14. data/lib/abstract_controller/translation.rb +11 -5
  15. data/lib/action_controller.rb +7 -4
  16. data/lib/action_controller/api.rb +4 -3
  17. data/lib/action_controller/base.rb +6 -9
  18. data/lib/action_controller/caching.rb +1 -3
  19. data/lib/action_controller/log_subscriber.rb +10 -7
  20. data/lib/action_controller/metal.rb +10 -8
  21. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  22. data/lib/action_controller/metal/conditional_get.rb +19 -5
  23. data/lib/action_controller/metal/content_security_policy.rb +1 -2
  24. data/lib/action_controller/metal/cookies.rb +3 -1
  25. data/lib/action_controller/metal/data_streaming.rb +6 -7
  26. data/lib/action_controller/metal/default_headers.rb +17 -0
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +3 -5
  28. data/lib/action_controller/metal/exceptions.rb +56 -2
  29. data/lib/action_controller/metal/flash.rb +5 -5
  30. data/lib/action_controller/metal/head.rb +7 -4
  31. data/lib/action_controller/metal/helpers.rb +14 -5
  32. data/lib/action_controller/metal/http_authentication.rb +24 -23
  33. data/lib/action_controller/metal/implicit_render.rb +5 -15
  34. data/lib/action_controller/metal/instrumentation.rb +13 -14
  35. data/lib/action_controller/metal/live.rb +30 -32
  36. data/lib/action_controller/metal/logging.rb +20 -0
  37. data/lib/action_controller/metal/mime_responds.rb +19 -4
  38. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  39. data/lib/action_controller/metal/params_wrapper.rb +31 -22
  40. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  41. data/lib/action_controller/metal/redirecting.rb +6 -6
  42. data/lib/action_controller/metal/renderers.rb +4 -4
  43. data/lib/action_controller/metal/rendering.rb +8 -3
  44. data/lib/action_controller/metal/request_forgery_protection.rb +62 -34
  45. data/lib/action_controller/metal/rescue.rb +1 -1
  46. data/lib/action_controller/metal/streaming.rb +0 -1
  47. data/lib/action_controller/metal/strong_parameters.rb +167 -58
  48. data/lib/action_controller/metal/url_for.rb +1 -1
  49. data/lib/action_controller/railties/helpers.rb +1 -1
  50. data/lib/action_controller/renderer.rb +37 -13
  51. data/lib/action_controller/template_assertions.rb +1 -1
  52. data/lib/action_controller/test_case.rb +70 -65
  53. data/lib/action_dispatch.rb +9 -3
  54. data/lib/action_dispatch/http/cache.rb +26 -21
  55. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  56. data/lib/action_dispatch/http/content_security_policy.rb +33 -19
  57. data/lib/action_dispatch/http/filter_parameters.rb +9 -8
  58. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  59. data/lib/action_dispatch/http/headers.rb +4 -4
  60. data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
  61. data/lib/action_dispatch/http/mime_type.rb +42 -23
  62. data/lib/action_dispatch/http/parameters.rb +14 -23
  63. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  64. data/lib/action_dispatch/http/request.rb +45 -22
  65. data/lib/action_dispatch/http/response.rb +45 -25
  66. data/lib/action_dispatch/http/upload.rb +9 -1
  67. data/lib/action_dispatch/http/url.rb +82 -82
  68. data/lib/action_dispatch/journey.rb +0 -2
  69. data/lib/action_dispatch/journey/formatter.rb +54 -30
  70. data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
  71. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  72. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
  73. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  74. data/lib/action_dispatch/journey/nodes/node.rb +13 -11
  75. data/lib/action_dispatch/journey/parser.rb +13 -13
  76. data/lib/action_dispatch/journey/parser.y +1 -1
  77. data/lib/action_dispatch/journey/path/pattern.rb +19 -21
  78. data/lib/action_dispatch/journey/route.rb +10 -20
  79. data/lib/action_dispatch/journey/router.rb +26 -34
  80. data/lib/action_dispatch/journey/router/utils.rb +14 -12
  81. data/lib/action_dispatch/journey/routes.rb +0 -2
  82. data/lib/action_dispatch/journey/scanner.rb +10 -4
  83. data/lib/action_dispatch/journey/visitors.rb +1 -4
  84. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  85. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  86. data/lib/action_dispatch/middleware/cookies.rb +128 -109
  87. data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
  88. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  89. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  90. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
  91. data/lib/action_dispatch/middleware/flash.rb +1 -1
  92. data/lib/action_dispatch/middleware/host_authorization.rb +121 -0
  93. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  94. data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
  95. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  96. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
  97. data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
  98. data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
  99. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  100. data/lib/action_dispatch/middleware/stack.rb +56 -2
  101. data/lib/action_dispatch/middleware/static.rb +153 -93
  102. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  103. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  105. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  107. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  108. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  109. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  111. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  112. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  113. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
  114. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -1
  115. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  119. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
  123. data/lib/action_dispatch/railtie.rb +8 -2
  124. data/lib/action_dispatch/request/session.rb +10 -9
  125. data/lib/action_dispatch/request/utils.rb +26 -2
  126. data/lib/action_dispatch/routing.rb +21 -20
  127. data/lib/action_dispatch/routing/inspector.rb +100 -52
  128. data/lib/action_dispatch/routing/mapper.rb +155 -103
  129. data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
  130. data/lib/action_dispatch/routing/redirection.rb +3 -3
  131. data/lib/action_dispatch/routing/route_set.rb +71 -69
  132. data/lib/action_dispatch/routing/url_for.rb +2 -2
  133. data/lib/action_dispatch/system_test_case.rb +54 -11
  134. data/lib/action_dispatch/system_testing/browser.rb +53 -16
  135. data/lib/action_dispatch/system_testing/driver.rb +11 -3
  136. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
  137. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
  138. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  139. data/lib/action_dispatch/testing/assertions.rb +1 -1
  140. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  141. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  142. data/lib/action_dispatch/testing/integration.rb +61 -28
  143. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  144. data/lib/action_dispatch/testing/test_process.rb +29 -4
  145. data/lib/action_dispatch/testing/test_request.rb +3 -3
  146. data/lib/action_dispatch/testing/test_response.rb +4 -32
  147. data/lib/action_pack.rb +1 -1
  148. data/lib/action_pack/gem_version.rb +4 -4
  149. metadata +38 -26
  150. data/lib/action_controller/metal/force_ssl.rb +0 -99
  151. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  152. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  153. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  154. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  155. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ module Http
5
+ class ContentDisposition # :nodoc:
6
+ def self.format(disposition:, filename:)
7
+ new(disposition: disposition, filename: filename).to_s
8
+ end
9
+
10
+ attr_reader :disposition, :filename
11
+
12
+ def initialize(disposition:, filename:)
13
+ @disposition = disposition
14
+ @filename = filename
15
+ end
16
+
17
+ TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!\#$+.^_`|~-]/
18
+
19
+ def ascii_filename
20
+ 'filename="' + percent_escape(I18n.transliterate(filename), TRADITIONAL_ESCAPED_CHAR) + '"'
21
+ end
22
+
23
+ RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!\#$&+.^_`|~-]/
24
+
25
+ def utf8_filename
26
+ "filename*=UTF-8''" + percent_escape(filename, RFC_5987_ESCAPED_CHAR)
27
+ end
28
+
29
+ def to_s
30
+ if filename
31
+ "#{disposition}; #{ascii_filename}; #{utf8_filename}"
32
+ else
33
+ "#{disposition}"
34
+ end
35
+ end
36
+
37
+ private
38
+ def percent_escape(string, pattern)
39
+ string.gsub(pattern) do |char|
40
+ char.bytes.map { |byte| "%%%02X" % byte }.join
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -5,9 +5,9 @@ require "active_support/core_ext/object/deep_dup"
5
5
  module ActionDispatch #:nodoc:
6
6
  class ContentSecurityPolicy
7
7
  class Middleware
8
- CONTENT_TYPE = "Content-Type".freeze
9
- POLICY = "Content-Security-Policy".freeze
10
- POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only".freeze
8
+ CONTENT_TYPE = "Content-Type"
9
+ POLICY = "Content-Security-Policy"
10
+ POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"
11
11
 
12
12
  def initialize(app)
13
13
  @app = app
@@ -22,18 +22,18 @@ module ActionDispatch #:nodoc:
22
22
 
23
23
  if policy = request.content_security_policy
24
24
  nonce = request.content_security_policy_nonce
25
+ nonce_directives = request.content_security_policy_nonce_directives
25
26
  context = request.controller_instance || request
26
- headers[header_name(request)] = policy.build(context, nonce)
27
+ headers[header_name(request)] = policy.build(context, nonce, nonce_directives)
27
28
  end
28
29
 
29
30
  response
30
31
  end
31
32
 
32
33
  private
33
-
34
34
  def html_response?(headers)
35
35
  if content_type = headers[CONTENT_TYPE]
36
- content_type =~ /html/
36
+ /html/.match?(content_type)
37
37
  end
38
38
  end
39
39
 
@@ -51,10 +51,11 @@ module ActionDispatch #:nodoc:
51
51
  end
52
52
 
53
53
  module Request
54
- POLICY = "action_dispatch.content_security_policy".freeze
55
- POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze
56
- NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator".freeze
57
- NONCE = "action_dispatch.content_security_policy_nonce".freeze
54
+ POLICY = "action_dispatch.content_security_policy"
55
+ POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only"
56
+ NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator"
57
+ NONCE = "action_dispatch.content_security_policy_nonce"
58
+ NONCE_DIRECTIVES = "action_dispatch.content_security_policy_nonce_directives"
58
59
 
59
60
  def content_security_policy
60
61
  get_header(POLICY)
@@ -80,6 +81,14 @@ module ActionDispatch #:nodoc:
80
81
  set_header(NONCE_GENERATOR, generator)
81
82
  end
82
83
 
84
+ def content_security_policy_nonce_directives
85
+ get_header(NONCE_DIRECTIVES)
86
+ end
87
+
88
+ def content_security_policy_nonce_directives=(generator)
89
+ set_header(NONCE_DIRECTIVES, generator)
90
+ end
91
+
83
92
  def content_security_policy_nonce
84
93
  if content_security_policy_nonce_generator
85
94
  if nonce = get_header(NONCE)
@@ -91,7 +100,6 @@ module ActionDispatch #:nodoc:
91
100
  end
92
101
 
93
102
  private
94
-
95
103
  def generate_content_security_policy_nonce
96
104
  content_security_policy_nonce_generator.call(self)
97
105
  end
@@ -127,14 +135,19 @@ module ActionDispatch #:nodoc:
127
135
  manifest_src: "manifest-src",
128
136
  media_src: "media-src",
129
137
  object_src: "object-src",
138
+ prefetch_src: "prefetch-src",
130
139
  script_src: "script-src",
140
+ script_src_attr: "script-src-attr",
141
+ script_src_elem: "script-src-elem",
131
142
  style_src: "style-src",
143
+ style_src_attr: "style-src-attr",
144
+ style_src_elem: "style-src-elem",
132
145
  worker_src: "worker-src"
133
146
  }.freeze
134
147
 
135
- NONCE_DIRECTIVES = %w[script-src].freeze
148
+ DEFAULT_NONCE_DIRECTIVES = %w[script-src style-src].freeze
136
149
 
137
- private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES
150
+ private_constant :MAPPINGS, :DIRECTIVES, :DEFAULT_NONCE_DIRECTIVES
138
151
 
139
152
  attr_reader :directives
140
153
 
@@ -203,8 +216,9 @@ module ActionDispatch #:nodoc:
203
216
  end
204
217
  end
205
218
 
206
- def build(context = nil, nonce = nil)
207
- build_directives(context, nonce).compact.join("; ")
219
+ def build(context = nil, nonce = nil, nonce_directives = nil)
220
+ nonce_directives = DEFAULT_NONCE_DIRECTIVES if nonce_directives.nil?
221
+ build_directives(context, nonce, nonce_directives).compact.join("; ")
208
222
  end
209
223
 
210
224
  private
@@ -227,10 +241,10 @@ module ActionDispatch #:nodoc:
227
241
  end
228
242
  end
229
243
 
230
- def build_directives(context, nonce)
244
+ def build_directives(context, nonce, nonce_directives)
231
245
  @directives.map do |directive, sources|
232
246
  if sources.is_a?(Array)
233
- if nonce && nonce_directive?(directive)
247
+ if nonce && nonce_directive?(directive, nonce_directives)
234
248
  "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'"
235
249
  else
236
250
  "#{directive} #{build_directive(sources, context).join(' ')}"
@@ -265,8 +279,8 @@ module ActionDispatch #:nodoc:
265
279
  end
266
280
  end
267
281
 
268
- def nonce_directive?(directive)
269
- NONCE_DIRECTIVES.include?(directive)
282
+ def nonce_directive?(directive, nonce_directives)
283
+ nonce_directives.include?(directive)
270
284
  end
271
285
  end
272
286
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_dispatch/http/parameter_filter"
3
+ require "active_support/parameter_filter"
4
4
 
5
5
  module ActionDispatch
6
6
  module Http
@@ -9,8 +9,8 @@ module ActionDispatch
9
9
  # sub-hashes of the params hash to filter. Filtering only certain sub-keys
10
10
  # from a hash is possible by using the dot notation: 'credit_card.number'.
11
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
+ # sub-hashes are passed to it, where the value or the key can be replaced using
13
+ # String#replace or similar methods.
14
14
  #
15
15
  # env["action_dispatch.parameter_filter"] = [:password]
16
16
  # => replaces the value to all keys matching /password/i with "[FILTERED]"
@@ -23,13 +23,13 @@ module ActionDispatch
23
23
  # change { file: { code: "xxxx"} }
24
24
  #
25
25
  # env["action_dispatch.parameter_filter"] = -> (k, v) do
26
- # v.reverse! if k =~ /secret/i
26
+ # v.reverse! if k.match?(/secret/i)
27
27
  # end
28
28
  # => reverses the value to all keys matching /secret/i
29
29
  module FilterParameters
30
30
  ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
31
- NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
32
- 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:
33
33
 
34
34
  def initialize
35
35
  super
@@ -41,6 +41,8 @@ module ActionDispatch
41
41
  # Returns a hash of parameters with all sensitive data replaced.
42
42
  def filtered_parameters
43
43
  @filtered_parameters ||= parameter_filter.filter(parameters)
44
+ rescue ActionDispatch::Http::Parameters::ParseError
45
+ @filtered_parameters = {}
44
46
  end
45
47
 
46
48
  # Returns a hash of request.env with all sensitive data replaced.
@@ -54,7 +56,6 @@ module ActionDispatch
54
56
  end
55
57
 
56
58
  private
57
-
58
59
  def parameter_filter # :doc:
59
60
  parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
60
61
  return NULL_PARAM_FILTER
@@ -69,7 +70,7 @@ module ActionDispatch
69
70
  end
70
71
 
71
72
  def parameter_filter_for(filters) # :doc:
72
- ParameterFilter.new(filters)
73
+ ActiveSupport::ParameterFilter.new(filters)
73
74
  end
74
75
 
75
76
  KV_RE = "[^&;=]+"
@@ -3,7 +3,7 @@
3
3
  module ActionDispatch
4
4
  module Http
5
5
  module FilterRedirect
6
- FILTERED = "[FILTERED]".freeze # :nodoc:
6
+ FILTERED = "[FILTERED]" # :nodoc:
7
7
 
8
8
  def filtered_location # :nodoc:
9
9
  if location_filter_match?
@@ -14,7 +14,6 @@ module ActionDispatch
14
14
  end
15
15
 
16
16
  private
17
-
18
17
  def location_filters
19
18
  if request
20
19
  request.get_header("action_dispatch.redirect_filter") || []
@@ -28,7 +27,7 @@ module ActionDispatch
28
27
  if String === filter
29
28
  location.include?(filter)
30
29
  elsif Regexp === filter
31
- location =~ filter
30
+ location.match?(filter)
32
31
  end
33
32
  end
34
33
  end
@@ -116,14 +116,14 @@ module ActionDispatch
116
116
  def env; @req.env.dup; end
117
117
 
118
118
  private
119
-
120
119
  # Converts an HTTP header name to an environment variable name if it is
121
120
  # not contained within the headers hash.
122
121
  def env_name(key)
123
122
  key = key.to_s
124
- if key =~ HTTP_HEADER
125
- key = key.upcase.tr("-", "_")
126
- key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
123
+ if HTTP_HEADER.match?(key)
124
+ key = key.upcase
125
+ key.tr!("-", "_")
126
+ key.prepend("HTTP_") unless CGI_VARIABLES.include?(key)
127
127
  end
128
128
  key
129
129
  end
@@ -7,6 +7,13 @@ module ActionDispatch
7
7
  module MimeNegotiation
8
8
  extend ActiveSupport::Concern
9
9
 
10
+ class InvalidType < ::Mime::Type::InvalidMimeType; end
11
+
12
+ RESCUABLE_MIME_FORMAT_ERRORS = [
13
+ ActionController::BadRequest,
14
+ ActionDispatch::Http::Parameters::ParseError,
15
+ ]
16
+
10
17
  included do
11
18
  mattr_accessor :ignore_accept_header, default: false
12
19
  end
@@ -20,6 +27,8 @@ module ActionDispatch
20
27
  nil
21
28
  end
22
29
  set_header k, v
30
+ rescue ::Mime::Type::InvalidMimeType => e
31
+ raise InvalidType, e.message
23
32
  end
24
33
  end
25
34
 
@@ -42,6 +51,8 @@ module ActionDispatch
42
51
  Mime::Type.parse(header)
43
52
  end
44
53
  set_header k, v
54
+ rescue ::Mime::Type::InvalidMimeType => e
55
+ raise InvalidType, e.message
45
56
  end
46
57
  end
47
58
 
@@ -57,13 +68,7 @@ module ActionDispatch
57
68
 
58
69
  def formats
59
70
  fetch_header("action_dispatch.request.formats") do |k|
60
- params_readable = begin
61
- parameters[:format]
62
- rescue ActionController::BadRequest
63
- false
64
- end
65
-
66
- v = if params_readable
71
+ v = if params_readable?
67
72
  Array(Mime[parameters[:format]])
68
73
  elsif use_accept_header && valid_accept_header
69
74
  accepts
@@ -90,10 +95,7 @@ module ActionDispatch
90
95
  if variant.all? { |v| v.is_a?(Symbol) }
91
96
  @variant = ActiveSupport::ArrayInquirer.new(variant)
92
97
  else
93
- raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols. " \
94
- "For security reasons, never directly set the variant to a user-provided value, " \
95
- "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
96
- "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."
97
99
  end
98
100
  end
99
101
 
@@ -151,13 +153,24 @@ module ActionDispatch
151
153
  order.include?(Mime::ALL) ? format : nil
152
154
  end
153
155
 
154
- private
156
+ def should_apply_vary_header?
157
+ !params_readable? && use_accept_header && valid_accept_header
158
+ end
155
159
 
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.
156
163
  BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
157
164
 
165
+ def params_readable? # :doc:
166
+ parameters[:format]
167
+ rescue *RESCUABLE_MIME_FORMAT_ERRORS
168
+ false
169
+ end
170
+
158
171
  def valid_accept_header # :doc:
159
172
  (xhr? && (accept.present? || content_mime_type)) ||
160
- (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
173
+ (accept.present? && !accept.match?(BROWSER_LIKE_ACCEPTS))
161
174
  end
162
175
 
163
176
  def use_accept_header # :doc:
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # -*- frozen-string-literal: true -*-
4
-
5
3
  require "singleton"
6
- require "active_support/core_ext/string/starts_ends_with"
4
+ require "active_support/core_ext/symbol/starts_ends_with"
7
5
 
8
6
  module Mime
9
7
  class Mimes
8
+ attr_reader :symbols
9
+
10
10
  include Enumerable
11
11
 
12
12
  def initialize
13
13
  @mimes = []
14
- @symbols = nil
14
+ @symbols = []
15
15
  end
16
16
 
17
17
  def each
@@ -20,15 +20,16 @@ module Mime
20
20
 
21
21
  def <<(type)
22
22
  @mimes << type
23
- @symbols = nil
23
+ @symbols << type.to_sym
24
24
  end
25
25
 
26
26
  def delete_if
27
- @mimes.delete_if { |x| yield x }.tap { @symbols = nil }
28
- end
29
-
30
- def symbols
31
- @symbols ||= map(&:to_sym)
27
+ @mimes.delete_if do |x|
28
+ if yield x
29
+ @symbols.delete(x.to_sym)
30
+ true
31
+ end
32
+ end
32
33
  end
33
34
  end
34
35
 
@@ -74,7 +75,7 @@ module Mime
74
75
  def initialize(index, name, q = nil)
75
76
  @index = index
76
77
  @name = name
77
- q ||= 0.0 if @name == "*/*".freeze # Default wildcard match to end of list.
78
+ q ||= 0.0 if @name == "*/*" # Default wildcard match to end of list.
78
79
  @q = ((q || 1.0).to_f * 100).to_i
79
80
  end
80
81
 
@@ -116,7 +117,7 @@ module Mime
116
117
  type = list[idx]
117
118
  break if type.q < app_xml.q
118
119
 
119
- if type.name.ends_with? "+xml"
120
+ if type.name.end_with? "+xml"
120
121
  list[app_xml_idx], list[idx] = list[idx], app_xml
121
122
  app_xml_idx = idx
122
123
  end
@@ -172,6 +173,7 @@ module Mime
172
173
  def parse(accept_header)
173
174
  if !accept_header.include?(",")
174
175
  accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
176
+ return [] unless accept_header
175
177
  parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
176
178
  else
177
179
  list, index = [], 0
@@ -203,7 +205,7 @@ module Mime
203
205
  # For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
204
206
  # Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]</tt>.
205
207
  def parse_data_with_trailing_star(type)
206
- Mime::SET.select { |m| m =~ type }
208
+ Mime::SET.select { |m| m.match?(type) }
207
209
  end
208
210
 
209
211
  # This method is opposite of register method.
@@ -223,7 +225,18 @@ module Mime
223
225
 
224
226
  attr_reader :hash
225
227
 
228
+ MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
229
+ MIME_PARAMETER_KEY = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
230
+ MIME_PARAMETER_VALUE = "#{Regexp.escape('"')}?[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}#{Regexp.escape('"')}?"
231
+ MIME_PARAMETER = "\s*\;\s*#{MIME_PARAMETER_KEY}(?:\=#{MIME_PARAMETER_VALUE})?"
232
+ MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?:\s*#{MIME_PARAMETER}\s*)*)\z/
233
+
234
+ class InvalidMimeType < StandardError; end
235
+
226
236
  def initialize(string, symbol = nil, synonyms = [])
237
+ unless MIME_REGEXP.match?(string)
238
+ raise InvalidMimeType, "#{string.inspect} is not a valid MIME type"
239
+ end
227
240
  @symbol, @synonyms = symbol, synonyms
228
241
  @string = string
229
242
  @hash = [@string, @synonyms, @symbol].hash
@@ -273,25 +286,27 @@ module Mime
273
286
  @synonyms.any? { |synonym| synonym.to_s =~ regexp } || @string =~ regexp
274
287
  end
275
288
 
289
+ def match?(mime_type)
290
+ return false unless mime_type
291
+ regexp = Regexp.new(Regexp.quote(mime_type.to_s))
292
+ @synonyms.any? { |synonym| synonym.to_s.match?(regexp) } || @string.match?(regexp)
293
+ end
294
+
276
295
  def html?
277
- symbol == :html || @string =~ /html/
296
+ (symbol == :html) || /html/.match?(@string)
278
297
  end
279
298
 
280
299
  def all?; false; end
281
300
 
282
- # TODO Change this to private once we've dropped Ruby 2.2 support.
283
- # Workaround for Ruby 2.2 "private attribute?" warning.
284
301
  protected
285
-
286
302
  attr_reader :string, :synonyms
287
303
 
288
304
  private
289
-
290
305
  def to_ary; end
291
306
  def to_a; end
292
307
 
293
308
  def method_missing(method, *args)
294
- if method.to_s.ends_with? "?"
309
+ if method.end_with?("?")
295
310
  method[0..-2].downcase.to_sym == to_sym
296
311
  else
297
312
  super
@@ -299,7 +314,7 @@ module Mime
299
314
  end
300
315
 
301
316
  def respond_to_missing?(method, include_private = false)
302
- (method.to_s.ends_with? "?") || super
317
+ method.end_with?("?") || super
303
318
  end
304
319
  end
305
320
 
@@ -307,7 +322,7 @@ module Mime
307
322
  include Singleton
308
323
 
309
324
  def initialize
310
- super "*/*", :all
325
+ super "*/*", nil
311
326
  end
312
327
 
313
328
  def all?; true; end
@@ -326,15 +341,19 @@ module Mime
326
341
  true
327
342
  end
328
343
 
344
+ def to_s
345
+ ""
346
+ end
347
+
329
348
  def ref; end
330
349
 
331
350
  private
332
351
  def respond_to_missing?(method, _)
333
- method.to_s.ends_with? "?"
352
+ method.end_with?("?")
334
353
  end
335
354
 
336
355
  def method_missing(method, *args)
337
- false if method.to_s.ends_with? "?"
356
+ false if method.end_with?("?")
338
357
  end
339
358
  end
340
359
  end