actionpack 6.0.4.1 → 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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -304
  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 +24 -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 +5 -1
  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/host_authorization.rb +29 -12
  71. data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
  72. data/lib/action_dispatch/middleware/request_id.rb +4 -5
  73. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
  74. data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
  75. data/lib/action_dispatch/middleware/ssl.rb +9 -6
  76. data/lib/action_dispatch/middleware/stack.rb +18 -0
  77. data/lib/action_dispatch/middleware/static.rb +154 -93
  78. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +18 -0
  79. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
  80. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
  81. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -3
  82. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +88 -8
  83. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  84. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +12 -1
  85. data/lib/action_dispatch/railtie.rb +3 -2
  86. data/lib/action_dispatch/request/session.rb +2 -8
  87. data/lib/action_dispatch/request/utils.rb +26 -2
  88. data/lib/action_dispatch/routing/inspector.rb +8 -7
  89. data/lib/action_dispatch/routing/mapper.rb +102 -71
  90. data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -19
  91. data/lib/action_dispatch/routing/redirection.rb +3 -3
  92. data/lib/action_dispatch/routing/route_set.rb +49 -41
  93. data/lib/action_dispatch/system_test_case.rb +29 -24
  94. data/lib/action_dispatch/system_testing/browser.rb +33 -27
  95. data/lib/action_dispatch/system_testing/driver.rb +6 -7
  96. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
  97. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
  98. data/lib/action_dispatch/testing/assertions/response.rb +2 -4
  99. data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
  100. data/lib/action_dispatch/testing/assertions.rb +1 -1
  101. data/lib/action_dispatch/testing/integration.rb +38 -27
  102. data/lib/action_dispatch/testing/test_process.rb +29 -4
  103. data/lib/action_dispatch/testing/test_request.rb +3 -3
  104. data/lib/action_dispatch.rb +3 -2
  105. data/lib/action_pack/gem_version.rb +3 -3
  106. data/lib/action_pack.rb +1 -1
  107. metadata +23 -24
  108. data/lib/action_controller/metal/force_ssl.rb +0 -58
  109. data/lib/action_dispatch/http/parameter_filter.rb +0 -12
  110. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  111. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  112. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -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]
@@ -33,7 +33,7 @@ module ActionDispatch #:nodoc:
33
33
  private
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
 
@@ -137,7 +137,11 @@ module ActionDispatch #:nodoc:
137
137
  object_src: "object-src",
138
138
  prefetch_src: "prefetch-src",
139
139
  script_src: "script-src",
140
+ script_src_attr: "script-src-attr",
141
+ script_src_elem: "script-src-elem",
140
142
  style_src: "style-src",
143
+ style_src_attr: "style-src-attr",
144
+ style_src_elem: "style-src-elem",
141
145
  worker_src: "worker-src"
142
146
  }.freeze
143
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