actionpack 6.0.6 → 6.1.0.rc1

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +233 -350
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/abstract_controller/base.rb +35 -2
  6. data/lib/abstract_controller/callbacks.rb +2 -2
  7. data/lib/abstract_controller/helpers.rb +105 -90
  8. data/lib/abstract_controller/rendering.rb +9 -9
  9. data/lib/abstract_controller/translation.rb +8 -2
  10. data/lib/abstract_controller.rb +1 -0
  11. data/lib/action_controller/api.rb +2 -2
  12. data/lib/action_controller/base.rb +4 -2
  13. data/lib/action_controller/caching.rb +0 -1
  14. data/lib/action_controller/log_subscriber.rb +3 -3
  15. data/lib/action_controller/metal/conditional_get.rb +10 -2
  16. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  17. data/lib/action_controller/metal/data_streaming.rb +1 -1
  18. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -4
  19. data/lib/action_controller/metal/exceptions.rb +33 -0
  20. data/lib/action_controller/metal/feature_policy.rb +46 -0
  21. data/lib/action_controller/metal/head.rb +7 -4
  22. data/lib/action_controller/metal/helpers.rb +11 -1
  23. data/lib/action_controller/metal/http_authentication.rb +5 -3
  24. data/lib/action_controller/metal/implicit_render.rb +1 -1
  25. data/lib/action_controller/metal/instrumentation.rb +11 -9
  26. data/lib/action_controller/metal/live.rb +1 -1
  27. data/lib/action_controller/metal/logging.rb +20 -0
  28. data/lib/action_controller/metal/mime_responds.rb +6 -2
  29. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  30. data/lib/action_controller/metal/params_wrapper.rb +16 -11
  31. data/lib/action_controller/metal/redirecting.rb +1 -1
  32. data/lib/action_controller/metal/rendering.rb +6 -0
  33. data/lib/action_controller/metal/request_forgery_protection.rb +1 -1
  34. data/lib/action_controller/metal/rescue.rb +1 -1
  35. data/lib/action_controller/metal/strong_parameters.rb +103 -15
  36. data/lib/action_controller/metal.rb +2 -2
  37. data/lib/action_controller/renderer.rb +23 -13
  38. data/lib/action_controller/test_case.rb +62 -56
  39. data/lib/action_controller.rb +2 -3
  40. data/lib/action_dispatch/http/cache.rb +12 -10
  41. data/lib/action_dispatch/http/content_security_policy.rb +11 -0
  42. data/lib/action_dispatch/http/feature_policy.rb +168 -0
  43. data/lib/action_dispatch/http/filter_parameters.rb +1 -1
  44. data/lib/action_dispatch/http/filter_redirect.rb +1 -1
  45. data/lib/action_dispatch/http/headers.rb +3 -2
  46. data/lib/action_dispatch/http/mime_negotiation.rb +14 -8
  47. data/lib/action_dispatch/http/mime_type.rb +29 -16
  48. data/lib/action_dispatch/http/parameters.rb +1 -19
  49. data/lib/action_dispatch/http/request.rb +24 -8
  50. data/lib/action_dispatch/http/response.rb +17 -16
  51. data/lib/action_dispatch/http/url.rb +3 -2
  52. data/lib/action_dispatch/journey/formatter.rb +53 -28
  53. data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
  54. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  55. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
  56. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  57. data/lib/action_dispatch/journey/nodes/node.rb +4 -3
  58. data/lib/action_dispatch/journey/parser.rb +13 -13
  59. data/lib/action_dispatch/journey/parser.y +1 -1
  60. data/lib/action_dispatch/journey/path/pattern.rb +13 -18
  61. data/lib/action_dispatch/journey/route.rb +7 -18
  62. data/lib/action_dispatch/journey/router/utils.rb +6 -4
  63. data/lib/action_dispatch/journey/router.rb +26 -30
  64. data/lib/action_dispatch/journey.rb +0 -2
  65. data/lib/action_dispatch/middleware/actionable_exceptions.rb +1 -1
  66. data/lib/action_dispatch/middleware/cookies.rb +67 -32
  67. data/lib/action_dispatch/middleware/debug_exceptions.rb +8 -15
  68. data/lib/action_dispatch/middleware/debug_view.rb +1 -1
  69. data/lib/action_dispatch/middleware/exception_wrapper.rb +28 -16
  70. data/lib/action_dispatch/middleware/executor.rb +1 -1
  71. data/lib/action_dispatch/middleware/host_authorization.rb +35 -35
  72. data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
  73. data/lib/action_dispatch/middleware/request_id.rb +4 -5
  74. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
  75. data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
  76. data/lib/action_dispatch/middleware/ssl.rb +9 -6
  77. data/lib/action_dispatch/middleware/stack.rb +18 -0
  78. data/lib/action_dispatch/middleware/static.rb +154 -93
  79. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +18 -0
  80. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -1
  81. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +1 -1
  82. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
  83. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
  84. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -3
  85. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +88 -8
  86. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  87. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +12 -1
  88. data/lib/action_dispatch/railtie.rb +3 -2
  89. data/lib/action_dispatch/request/session.rb +2 -8
  90. data/lib/action_dispatch/request/utils.rb +26 -2
  91. data/lib/action_dispatch/routing/inspector.rb +8 -7
  92. data/lib/action_dispatch/routing/mapper.rb +102 -71
  93. data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -19
  94. data/lib/action_dispatch/routing/redirection.rb +3 -3
  95. data/lib/action_dispatch/routing/route_set.rb +49 -41
  96. data/lib/action_dispatch/system_test_case.rb +29 -24
  97. data/lib/action_dispatch/system_testing/browser.rb +33 -27
  98. data/lib/action_dispatch/system_testing/driver.rb +6 -7
  99. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
  100. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
  101. data/lib/action_dispatch/testing/assertions/response.rb +2 -4
  102. data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
  103. data/lib/action_dispatch/testing/assertions.rb +1 -1
  104. data/lib/action_dispatch/testing/integration.rb +38 -27
  105. data/lib/action_dispatch/testing/test_process.rb +29 -4
  106. data/lib/action_dispatch/testing/test_request.rb +3 -3
  107. data/lib/action_dispatch.rb +3 -2
  108. data/lib/action_pack/gem_version.rb +3 -3
  109. data/lib/action_pack.rb +1 -1
  110. metadata +23 -25
  111. data/lib/action_controller/metal/force_ssl.rb +0 -58
  112. data/lib/action_dispatch/http/parameter_filter.rb +0 -12
  113. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  114. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  115. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -126,7 +126,7 @@ module ActionController
126
126
  # ==== Returns
127
127
  # * <tt>string</tt>
128
128
  def self.controller_name
129
- @controller_name ||= name.demodulize.sub(/Controller$/, "").underscore
129
+ @controller_name ||= (name.demodulize.delete_suffix("Controller").underscore unless anonymous?)
130
130
  end
131
131
 
132
132
  def self.make_response!(request)
@@ -135,7 +135,7 @@ module ActionController
135
135
  end
136
136
  end
137
137
 
138
- def self.binary_params_for?(action) # :nodoc:
138
+ def self.action_encoding_template(action) # :nodoc:
139
139
  false
140
140
  end
141
141
 
@@ -65,7 +65,7 @@ module ActionController
65
65
  def initialize(controller, env, defaults)
66
66
  @controller = controller
67
67
  @defaults = defaults
68
- @env = normalize_keys defaults.merge(env)
68
+ @env = normalize_keys defaults, env
69
69
  end
70
70
 
71
71
  # Render templates with any options from ActionController::Base#render_to_string.
@@ -82,8 +82,12 @@ module ActionController
82
82
  # need to call <tt>.to_json</tt> on the object you want to render.
83
83
  # * <tt>:body</tt> - Renders provided text and sets content type of <tt>text/plain</tt>.
84
84
  #
85
- # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, the default is
86
- # to render a partial and use the second parameter as the locals hash.
85
+ # If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
86
+ #
87
+ # If an object responding to `render_in` is passed, `render_in` is called on the object,
88
+ # passing in the current view context.
89
+ #
90
+ # Otherwise, a partial is rendered using the second parameter as the locals hash.
87
91
  def render(*args)
88
92
  raise "missing controller" unless controller
89
93
 
@@ -98,9 +102,15 @@ module ActionController
98
102
  alias_method :render_to_string, :render # :nodoc:
99
103
 
100
104
  private
101
- def normalize_keys(env)
105
+ def normalize_keys(defaults, env)
102
106
  new_env = {}
103
107
  env.each_pair { |k, v| new_env[rack_key_for(k)] = rack_value_for(k, v) }
108
+
109
+ defaults.each_pair do |k, v|
110
+ key = rack_key_for(k)
111
+ new_env[key] = rack_value_for(k, v) unless new_env.key?(key)
112
+ end
113
+
104
114
  new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http"
105
115
  new_env
106
116
  end
@@ -113,19 +123,19 @@ module ActionController
113
123
  input: "rack.input"
114
124
  }
115
125
 
116
- IDENTITY = ->(_) { _ }
117
-
118
- RACK_VALUE_TRANSLATION = {
119
- https: ->(v) { v ? "on" : "off" },
120
- method: ->(v) { -v.upcase },
121
- }
122
-
123
126
  def rack_key_for(key)
124
- RACK_KEY_TRANSLATION.fetch(key, key.to_s)
127
+ RACK_KEY_TRANSLATION[key] || key.to_s
125
128
  end
126
129
 
127
130
  def rack_value_for(key, value)
128
- RACK_VALUE_TRANSLATION.fetch(key, IDENTITY).call value
131
+ case key
132
+ when :https
133
+ value ? "on" : "off"
134
+ when :method
135
+ -value.upcase
136
+ else
137
+ value
138
+ end
129
139
  end
130
140
  end
131
141
  end
@@ -84,7 +84,7 @@ module ActionController
84
84
  value = value.to_param
85
85
  end
86
86
 
87
- path_parameters[key] = value
87
+ path_parameters[key.to_sym] = value
88
88
  end
89
89
  end
90
90
 
@@ -492,57 +492,8 @@ module ActionController
492
492
  parameters[:format] = format
493
493
  end
494
494
 
495
- generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action))
496
- generated_path = generated_path(generated_extras)
497
- query_string_keys = query_parameter_names(generated_extras)
498
-
499
- @request.assign_parameters(@routes, controller_class_name, action, parameters, generated_path, query_string_keys)
500
-
501
- @request.session.update(session) if session
502
- @request.flash.update(flash || {})
503
-
504
- if xhr
505
- @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
506
- @request.fetch_header("HTTP_ACCEPT") do |k|
507
- @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
508
- end
509
- end
510
-
511
- @request.fetch_header("SCRIPT_NAME") do |k|
512
- @request.set_header k, @controller.config.relative_url_root
513
- end
514
-
515
- begin
516
- @controller.recycle!
517
- @controller.dispatch(action, @request, @response)
518
- ensure
519
- @request = @controller.request
520
- @response = @controller.response
521
-
522
- if @request.have_cookie_jar?
523
- unless @request.cookie_jar.committed?
524
- @request.cookie_jar.write(@response)
525
- cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
526
- end
527
- end
528
- @response.prepare!
529
-
530
- if flash_value = @request.flash.to_session_value
531
- @request.session["flash"] = flash_value
532
- else
533
- @request.session.delete("flash")
534
- end
535
-
536
- if xhr
537
- @request.delete_header "HTTP_X_REQUESTED_WITH"
538
- @request.delete_header "HTTP_ACCEPT"
539
- end
540
- @request.query_string = ""
541
-
542
- @response.sent!
543
- end
544
-
545
- @response
495
+ setup_request(controller_class_name, action, parameters, session, flash, xhr)
496
+ process_controller_response(action, cookies, xhr)
546
497
  end
547
498
 
548
499
  def controller_class_name
@@ -598,11 +549,66 @@ module ActionController
598
549
  end
599
550
 
600
551
  private
552
+ def setup_request(controller_class_name, action, parameters, session, flash, xhr)
553
+ generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action))
554
+ generated_path = generated_path(generated_extras)
555
+ query_string_keys = query_parameter_names(generated_extras)
556
+
557
+ @request.assign_parameters(@routes, controller_class_name, action, parameters, generated_path, query_string_keys)
558
+
559
+ @request.session.update(session) if session
560
+ @request.flash.update(flash || {})
561
+
562
+ if xhr
563
+ @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
564
+ @request.fetch_header("HTTP_ACCEPT") do |k|
565
+ @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
566
+ end
567
+ end
568
+
569
+ @request.fetch_header("SCRIPT_NAME") do |k|
570
+ @request.set_header k, @controller.config.relative_url_root
571
+ end
572
+ end
573
+
574
+ def process_controller_response(action, cookies, xhr)
575
+ begin
576
+ @controller.recycle!
577
+ @controller.dispatch(action, @request, @response)
578
+ ensure
579
+ @request = @controller.request
580
+ @response = @controller.response
581
+
582
+ if @request.have_cookie_jar?
583
+ unless @request.cookie_jar.committed?
584
+ @request.cookie_jar.write(@response)
585
+ cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
586
+ end
587
+ end
588
+ @response.prepare!
589
+
590
+ if flash_value = @request.flash.to_session_value
591
+ @request.session["flash"] = flash_value
592
+ else
593
+ @request.session.delete("flash")
594
+ end
595
+
596
+ if xhr
597
+ @request.delete_header "HTTP_X_REQUESTED_WITH"
598
+ @request.delete_header "HTTP_ACCEPT"
599
+ end
600
+ @request.query_string = ""
601
+
602
+ @response.sent!
603
+ end
604
+
605
+ @response
606
+ end
607
+
601
608
  def scrub_env!(env)
602
- env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
603
- env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
604
- env.delete "action_dispatch.request.query_parameters"
605
- env.delete "action_dispatch.request.request_parameters"
609
+ env.delete_if do |k, _|
610
+ k.start_with?("rack.request", "action_dispatch.request", "action_dispatch.rescue")
611
+ end
606
612
  env["rack.input"] = StringIO.new
607
613
  env.delete "CONTENT_LENGTH"
608
614
  env.delete "RAW_POST_DATA"
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/rails"
4
3
  require "abstract_controller"
5
4
  require "action_dispatch"
6
5
  require "action_controller/metal/strong_parameters"
@@ -11,7 +10,6 @@ module ActionController
11
10
  autoload :API
12
11
  autoload :Base
13
12
  autoload :Metal
14
- autoload :Middleware
15
13
  autoload :Renderer
16
14
  autoload :FormBuilder
17
15
 
@@ -31,14 +29,15 @@ module ActionController
31
29
  autoload :DefaultHeaders
32
30
  autoload :EtagWithTemplateDigest
33
31
  autoload :EtagWithFlash
32
+ autoload :FeaturePolicy
34
33
  autoload :Flash
35
- autoload :ForceSSL
36
34
  autoload :Head
37
35
  autoload :Helpers
38
36
  autoload :HttpAuthentication
39
37
  autoload :BasicImplicitRender
40
38
  autoload :ImplicitRender
41
39
  autoload :Instrumentation
40
+ autoload :Logging
42
41
  autoload :MimeResponds
43
42
  autoload :ParamsWrapper
44
43
  autoload :Redirecting
@@ -114,7 +114,7 @@ module ActionDispatch
114
114
 
115
115
  # True if an ETag is set and it's a weak validator (preceded with W/)
116
116
  def weak_etag?
117
- etag? && etag.starts_with?('W/"')
117
+ etag? && etag.start_with?('W/"')
118
118
  end
119
119
 
120
120
  # True if an ETag is set and it isn't a weak validator (not preceded with W/)
@@ -125,7 +125,7 @@ module ActionDispatch
125
125
  private
126
126
  DATE = "Date"
127
127
  LAST_MODIFIED = "Last-Modified"
128
- SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
128
+ SPECIAL_KEYS = Set.new(%w[extras no-store no-cache max-age public private must-revalidate])
129
129
 
130
130
  def generate_weak_etag(validators)
131
131
  "W/#{generate_strong_etag(validators)}"
@@ -150,8 +150,8 @@ module ActionDispatch
150
150
  directive, argument = segment.split("=", 2)
151
151
 
152
152
  if SPECIAL_KEYS.include? directive
153
- key = directive.tr("-", "_")
154
- cache_control[key.to_sym] = argument || true
153
+ directive.tr!("-", "_")
154
+ cache_control[directive.to_sym] = argument || true
155
155
  else
156
156
  cache_control[:extras] ||= []
157
157
  cache_control[:extras] << segment
@@ -166,6 +166,7 @@ module ActionDispatch
166
166
  end
167
167
 
168
168
  DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
169
+ NO_STORE = "no-store"
169
170
  NO_CACHE = "no-cache"
170
171
  PUBLIC = "public"
171
172
  PRIVATE = "private"
@@ -182,19 +183,20 @@ module ActionDispatch
182
183
  end
183
184
 
184
185
  def merge_and_normalize_cache_control!(cache_control)
185
- control = {}
186
- cc_headers = cache_control_headers
187
- if extras = cc_headers.delete(:extras)
186
+ control = cache_control_headers
187
+
188
+ return if control.empty? && cache_control.empty? # Let middleware handle default behavior
189
+
190
+ if extras = control.delete(:extras)
188
191
  cache_control[:extras] ||= []
189
192
  cache_control[:extras] += extras
190
193
  cache_control[:extras].uniq!
191
194
  end
192
195
 
193
- control.merge! cc_headers
194
196
  control.merge! cache_control
195
197
 
196
- if control.empty?
197
- # Let middleware handle default behavior
198
+ if control[:no_store]
199
+ self._cache_control = NO_STORE
198
200
  elsif control[:no_cache]
199
201
  options = []
200
202
  options << PUBLIC if control[:public]
@@ -17,6 +17,7 @@ module ActionDispatch #:nodoc:
17
17
  request = ActionDispatch::Request.new env
18
18
  _, headers, _ = response = @app.call(env)
19
19
 
20
+ return response unless html_response?(headers)
20
21
  return response if policy_present?(headers)
21
22
 
22
23
  if policy = request.content_security_policy
@@ -30,6 +31,12 @@ module ActionDispatch #:nodoc:
30
31
  end
31
32
 
32
33
  private
34
+ def html_response?(headers)
35
+ if content_type = headers[CONTENT_TYPE]
36
+ /html/.match?(content_type)
37
+ end
38
+ end
39
+
33
40
  def header_name(request)
34
41
  if request.content_security_policy_report_only
35
42
  POLICY_REPORT_ONLY
@@ -130,7 +137,11 @@ module ActionDispatch #:nodoc:
130
137
  object_src: "object-src",
131
138
  prefetch_src: "prefetch-src",
132
139
  script_src: "script-src",
140
+ script_src_attr: "script-src-attr",
141
+ script_src_elem: "script-src-elem",
133
142
  style_src: "style-src",
143
+ style_src_attr: "style-src-attr",
144
+ style_src_elem: "style-src-elem",
134
145
  worker_src: "worker-src"
135
146
  }.freeze
136
147
 
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/deep_dup"
4
+
5
+ module ActionDispatch #:nodoc:
6
+ class FeaturePolicy
7
+ class Middleware
8
+ CONTENT_TYPE = "Content-Type"
9
+ POLICY = "Feature-Policy"
10
+
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ request = ActionDispatch::Request.new(env)
17
+ _, headers, _ = response = @app.call(env)
18
+
19
+ return response unless html_response?(headers)
20
+ return response if policy_present?(headers)
21
+
22
+ if policy = request.feature_policy
23
+ headers[POLICY] = policy.build(request.controller_instance)
24
+ end
25
+
26
+ if policy_empty?(policy)
27
+ headers.delete(POLICY)
28
+ end
29
+
30
+ response
31
+ end
32
+
33
+ private
34
+ def html_response?(headers)
35
+ if content_type = headers[CONTENT_TYPE]
36
+ /html/.match?(content_type)
37
+ end
38
+ end
39
+
40
+ def policy_present?(headers)
41
+ headers[POLICY]
42
+ end
43
+
44
+ def policy_empty?(policy)
45
+ policy&.directives&.empty?
46
+ end
47
+ end
48
+
49
+ module Request
50
+ POLICY = "action_dispatch.feature_policy"
51
+
52
+ def feature_policy
53
+ get_header(POLICY)
54
+ end
55
+
56
+ def feature_policy=(policy)
57
+ set_header(POLICY, policy)
58
+ end
59
+ end
60
+
61
+ MAPPINGS = {
62
+ self: "'self'",
63
+ none: "'none'",
64
+ }.freeze
65
+
66
+ # List of available features can be found at
67
+ # https://github.com/WICG/feature-policy/blob/master/features.md#policy-controlled-features
68
+ DIRECTIVES = {
69
+ accelerometer: "accelerometer",
70
+ ambient_light_sensor: "ambient-light-sensor",
71
+ autoplay: "autoplay",
72
+ camera: "camera",
73
+ encrypted_media: "encrypted-media",
74
+ fullscreen: "fullscreen",
75
+ geolocation: "geolocation",
76
+ gyroscope: "gyroscope",
77
+ magnetometer: "magnetometer",
78
+ microphone: "microphone",
79
+ midi: "midi",
80
+ payment: "payment",
81
+ picture_in_picture: "picture-in-picture",
82
+ speaker: "speaker",
83
+ usb: "usb",
84
+ vibrate: "vibrate",
85
+ vr: "vr",
86
+ }.freeze
87
+
88
+ private_constant :MAPPINGS, :DIRECTIVES
89
+
90
+ attr_reader :directives
91
+
92
+ def initialize
93
+ @directives = {}
94
+ yield self if block_given?
95
+ end
96
+
97
+ def initialize_copy(other)
98
+ @directives = other.directives.deep_dup
99
+ end
100
+
101
+ DIRECTIVES.each do |name, directive|
102
+ define_method(name) do |*sources|
103
+ if sources.first
104
+ @directives[directive] = apply_mappings(sources)
105
+ else
106
+ @directives.delete(directive)
107
+ end
108
+ end
109
+ end
110
+
111
+ def build(context = nil)
112
+ build_directives(context).compact.join("; ")
113
+ end
114
+
115
+ private
116
+ def apply_mappings(sources)
117
+ sources.map do |source|
118
+ case source
119
+ when Symbol
120
+ apply_mapping(source)
121
+ when String, Proc
122
+ source
123
+ else
124
+ raise ArgumentError, "Invalid HTTP feature policy source: #{source.inspect}"
125
+ end
126
+ end
127
+ end
128
+
129
+ def apply_mapping(source)
130
+ MAPPINGS.fetch(source) do
131
+ raise ArgumentError, "Unknown HTTP feature policy source mapping: #{source.inspect}"
132
+ end
133
+ end
134
+
135
+ def build_directives(context)
136
+ @directives.map do |directive, sources|
137
+ if sources.is_a?(Array)
138
+ "#{directive} #{build_directive(sources, context).join(' ')}"
139
+ elsif sources
140
+ directive
141
+ else
142
+ nil
143
+ end
144
+ end
145
+ end
146
+
147
+ def build_directive(sources, context)
148
+ sources.map { |source| resolve_source(source, context) }
149
+ end
150
+
151
+ def resolve_source(source, context)
152
+ case source
153
+ when String
154
+ source
155
+ when Symbol
156
+ source.to_s
157
+ when Proc
158
+ if context.nil?
159
+ raise RuntimeError, "Missing context for the dynamic feature policy source: #{source.inspect}"
160
+ else
161
+ context.instance_exec(&source)
162
+ end
163
+ else
164
+ raise RuntimeError, "Unexpected feature policy source: #{source.inspect}"
165
+ end
166
+ end
167
+ end
168
+ end
@@ -23,7 +23,7 @@ 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
@@ -27,7 +27,7 @@ module ActionDispatch
27
27
  if String === filter
28
28
  location.include?(filter)
29
29
  elsif Regexp === filter
30
- location =~ filter
30
+ location.match?(filter)
31
31
  end
32
32
  end
33
33
  end
@@ -121,8 +121,9 @@ module ActionDispatch
121
121
  def env_name(key)
122
122
  key = key.to_s
123
123
  if HTTP_HEADER.match?(key)
124
- key = key.upcase.tr("-", "_")
125
- key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
124
+ key = key.upcase
125
+ key.tr!("-", "_")
126
+ key.prepend("HTTP_") unless CGI_VARIABLES.include?(key)
126
127
  end
127
128
  key
128
129
  end
@@ -68,13 +68,7 @@ module ActionDispatch
68
68
 
69
69
  def formats
70
70
  fetch_header("action_dispatch.request.formats") do |k|
71
- params_readable = begin
72
- parameters[:format]
73
- rescue *RESCUABLE_MIME_FORMAT_ERRORS
74
- false
75
- end
76
-
77
- v = if params_readable
71
+ v = if params_readable?
78
72
  Array(Mime[parameters[:format]])
79
73
  elsif use_accept_header && valid_accept_header
80
74
  accepts
@@ -159,12 +153,24 @@ module ActionDispatch
159
153
  order.include?(Mime::ALL) ? format : nil
160
154
  end
161
155
 
156
+ def should_apply_vary_header?
157
+ !params_readable? && use_accept_header && valid_accept_header
158
+ end
159
+
162
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
163
  BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
164
164
 
165
+ def params_readable? # :doc:
166
+ parameters[:format]
167
+ rescue *RESCUABLE_MIME_FORMAT_ERRORS
168
+ false
169
+ end
170
+
165
171
  def valid_accept_header # :doc:
166
172
  (xhr? && (accept.present? || content_mime_type)) ||
167
- (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
173
+ (accept.present? && !accept.match?(BROWSER_LIKE_ACCEPTS))
168
174
  end
169
175
 
170
176
  def use_accept_header # :doc: