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
@@ -9,17 +9,6 @@ module ActionDispatch
9
9
  " #{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
10
10
  }
11
11
 
12
- # memo_nodes = memos.values.flatten.map { |n|
13
- # label = n
14
- # if Journey::Route === n
15
- # label = "#{n.verb.source} #{n.path.spec}"
16
- # end
17
- # " #{n.object_id} [label=\"#{label}\", shape=box];"
18
- # }
19
- # memo_edges = memos.flat_map { |k, memos|
20
- # (memos || []).map { |v| " #{k} -> #{v.object_id};" }
21
- # }.uniq
22
-
23
12
  <<-eodot
24
13
  digraph nfa {
25
14
  rankdir=LR;
@@ -79,9 +79,10 @@ module ActionDispatch
79
79
  attr_reader :name
80
80
 
81
81
  DEFAULT_EXP = /[^\.\/\?]+/
82
- def initialize(left)
83
- super
84
- @regexp = DEFAULT_EXP
82
+ GREEDY_EXP = /(.+)/
83
+ def initialize(left, regexp = DEFAULT_EXP)
84
+ super(left)
85
+ @regexp = regexp
85
86
  @name = -left.tr("*:", "")
86
87
  end
87
88
 
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # DO NOT MODIFY!!!!
3
- # This file is automatically generated by Racc 1.4.14
3
+ # This file is automatically generated by Racc 1.4.16
4
4
  # from Racc grammar file "".
5
5
  #
6
6
 
@@ -135,11 +135,11 @@ Racc_debug_parser = false
135
135
  # reduce 0 omitted
136
136
 
137
137
  def _reduce_1(val, _values)
138
- Cat.new(val.first, val.last)
138
+ Cat.new(val.first, val.last)
139
139
  end
140
140
 
141
141
  def _reduce_2(val, _values)
142
- val.first
142
+ val.first
143
143
  end
144
144
 
145
145
  # reduce 3 omitted
@@ -151,19 +151,19 @@ end
151
151
  # reduce 6 omitted
152
152
 
153
153
  def _reduce_7(val, _values)
154
- Group.new(val[1])
154
+ Group.new(val[1])
155
155
  end
156
156
 
157
157
  def _reduce_8(val, _values)
158
- Or.new([val.first, val.last])
158
+ Or.new([val.first, val.last])
159
159
  end
160
160
 
161
161
  def _reduce_9(val, _values)
162
- Or.new([val.first, val.last])
162
+ Or.new([val.first, val.last])
163
163
  end
164
164
 
165
165
  def _reduce_10(val, _values)
166
- Star.new(Symbol.new(val.last))
166
+ Star.new(Symbol.new(val.last, Symbol::GREEDY_EXP))
167
167
  end
168
168
 
169
169
  # reduce 11 omitted
@@ -175,19 +175,19 @@ end
175
175
  # reduce 14 omitted
176
176
 
177
177
  def _reduce_15(val, _values)
178
- Slash.new(val.first)
178
+ Slash.new(val.first)
179
179
  end
180
180
 
181
181
  def _reduce_16(val, _values)
182
- Symbol.new(val.first)
182
+ Symbol.new(val.first)
183
183
  end
184
184
 
185
185
  def _reduce_17(val, _values)
186
- Literal.new(val.first)
186
+ Literal.new(val.first)
187
187
  end
188
188
 
189
189
  def _reduce_18(val, _values)
190
- Dot.new(val.first)
190
+ Dot.new(val.first)
191
191
  end
192
192
 
193
193
  def _reduce_none(val, _values)
@@ -195,5 +195,5 @@ def _reduce_none(val, _values)
195
195
  end
196
196
 
197
197
  end # class Parser
198
- end # module Journey
199
- end # module ActionDispatch
198
+ end # module Journey
199
+ end # module ActionDispatch
@@ -21,7 +21,7 @@ rule
21
21
  | expression OR or { Or.new([val.first, val.last]) }
22
22
  ;
23
23
  star
24
- : STAR { Star.new(Symbol.new(val.last)) }
24
+ : STAR { Star.new(Symbol.new(val.last, Symbol::GREEDY_EXP)) }
25
25
  ;
26
26
  terminal
27
27
  : symbol
@@ -6,16 +6,6 @@ module ActionDispatch
6
6
  class Pattern # :nodoc:
7
7
  attr_reader :spec, :requirements, :anchored
8
8
 
9
- def self.from_string(string)
10
- build(string, {}, "/.?", true)
11
- end
12
-
13
- def self.build(path, requirements, separators, anchored)
14
- parser = Journey::Parser.new
15
- ast = parser.parse path
16
- new ast, requirements, separators, anchored
17
- end
18
-
19
9
  def initialize(ast, requirements, separators, anchored)
20
10
  @spec = ast
21
11
  @requirements = requirements
@@ -46,11 +36,6 @@ module ActionDispatch
46
36
  node.regexp = re if re
47
37
  end
48
38
 
49
- @spec.find_all(&:star?).each do |node|
50
- node = node.left
51
- node.regexp = @requirements[node.to_sym] || /(.+)/
52
- end
53
-
54
39
  @spec
55
40
  end
56
41
 
@@ -81,7 +66,7 @@ module ActionDispatch
81
66
  end
82
67
 
83
68
  def visit_CAT(node)
84
- [visit(node.left), visit(node.right)].join
69
+ "#{visit(node.left)}#{visit(node.right)}"
85
70
  end
86
71
 
87
72
  def visit_SYMBOL(node)
@@ -107,8 +92,8 @@ module ActionDispatch
107
92
  end
108
93
 
109
94
  def visit_STAR(node)
110
- re = @matchers[node.left.to_sym] || ".+"
111
- "(#{re})"
95
+ re = @matchers[node.left.to_sym]
96
+ re ? "(#{re})" : "(.+)"
112
97
  end
113
98
 
114
99
  def visit_OR(node)
@@ -165,6 +150,10 @@ module ActionDispatch
165
150
  end
166
151
  alias :=~ :match
167
152
 
153
+ def match?(other)
154
+ to_regexp.match?(other)
155
+ end
156
+
168
157
  def source
169
158
  to_regexp.source
170
159
  end
@@ -173,6 +162,12 @@ module ActionDispatch
173
162
  @re ||= regexp_visitor.new(@separators, @requirements).accept spec
174
163
  end
175
164
 
165
+ def requirements_for_missing_keys_check
166
+ @requirements_for_missing_keys_check ||= requirements.transform_values do |regex|
167
+ /\A#{regex}\Z/
168
+ end
169
+ end
170
+
176
171
  private
177
172
  def regexp_visitor
178
173
  @anchored ? AnchoredRegexp : UnanchoredRegexp
@@ -13,6 +13,7 @@ module ActionDispatch
13
13
  VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
14
14
  VERBS.each do |v|
15
15
  class_eval <<-eoc, __FILE__, __LINE__ + 1
16
+ # frozen_string_literal: true
16
17
  class #{v}
17
18
  def self.verb; name.split("::").last; end
18
19
  def self.call(req); req.#{v.downcase}?; end
@@ -27,7 +28,7 @@ module ActionDispatch
27
28
  @verb = verb
28
29
  end
29
30
 
30
- def call(request); @verb === request.request_method; end
31
+ def call(request); @verb == request.request_method; end
31
32
  end
32
33
 
33
34
  class All
@@ -49,15 +50,10 @@ module ActionDispatch
49
50
  end
50
51
  end
51
52
 
52
- def self.build(name, app, path, constraints, required_defaults, defaults)
53
- request_method_match = verb_matcher(constraints.delete(:request_method))
54
- new name, app, path, constraints, required_defaults, defaults, request_method_match, 0, {}
55
- end
56
-
57
53
  ##
58
54
  # +path+ is a path constraint.
59
55
  # +constraints+ is a hash of constraints to be applied to this route.
60
- def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, scope_options, internal = false)
56
+ def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false)
61
57
  @name = name
62
58
  @app = app
63
59
  @path = path
@@ -92,7 +88,7 @@ module ActionDispatch
92
88
  end
93
89
  end
94
90
 
95
- # Needed for `rails routes`. Picks up succinctly defined requirements
91
+ # Needed for `bin/rails routes`. Picks up succinctly defined requirements
96
92
  # for a route, for example route
97
93
  #
98
94
  # get 'photo/:id', :controller => 'photos', :action => 'show',
@@ -115,18 +111,11 @@ module ActionDispatch
115
111
  end
116
112
 
117
113
  def score(supplied_keys)
118
- required_keys = path.required_names
119
-
120
- required_keys.each do |k|
114
+ path.required_names.each do |k|
121
115
  return -1 unless supplied_keys.include?(k)
122
116
  end
123
117
 
124
- score = 0
125
- path.names.each do |k|
126
- score += 1 if supplied_keys.include?(k)
127
- end
128
-
129
- score + (required_defaults.length * 2)
118
+ (required_defaults.length * 2) + path.names.count { |k| supplied_keys.include?(k) }
130
119
  end
131
120
 
132
121
  def parts
@@ -153,7 +142,7 @@ module ActionDispatch
153
142
  end
154
143
 
155
144
  def glob?
156
- !path.spec.grep(Nodes::Star).empty?
145
+ path.spec.any?(Nodes::Star)
157
146
  end
158
147
 
159
148
  def dispatcher?
@@ -19,11 +19,13 @@ module ActionDispatch
19
19
  encoding = path.encoding
20
20
  path = +"/#{path}"
21
21
  path.squeeze!("/")
22
- path.sub!(%r{/+\Z}, "")
23
- path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
24
- path = +"/" if path == ""
22
+
23
+ unless path == "/"
24
+ path.delete_suffix!("/")
25
+ path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
26
+ end
27
+
25
28
  path.force_encoding(encoding)
26
- path
27
29
  end
28
30
 
29
31
  # URI path and fragment escaping
@@ -40,11 +40,12 @@ module ActionDispatch
40
40
  req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
41
41
  end
42
42
 
43
- parameters = route.defaults.merge parameters.transform_values { |val|
44
- val.dup.force_encoding(::Encoding::UTF_8)
43
+ tmp_params = set_params.merge route.defaults
44
+ parameters.each_pair { |key, val|
45
+ tmp_params[key] = val.force_encoding(::Encoding::UTF_8)
45
46
  }
46
47
 
47
- req.path_parameters = set_params.merge parameters
48
+ req.path_parameters = tmp_params
48
49
 
49
50
  status, headers, body = route.app.serve(req)
50
51
 
@@ -65,7 +66,8 @@ module ActionDispatch
65
66
  find_routes(rails_req).each do |match, parameters, route|
66
67
  unless route.path.anchored
67
68
  rails_req.script_name = match.to_s
68
- rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1')
69
+ rails_req.path_info = match.post_match
70
+ rails_req.path_info = "/" + rails_req.path_info unless rails_req.path_info.start_with? "/"
69
71
  end
70
72
 
71
73
  parameters = route.defaults.merge parameters
@@ -105,23 +107,24 @@ module ActionDispatch
105
107
  end
106
108
 
107
109
  def find_routes(req)
108
- routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
109
- r.path.match(req.path_info)
110
+ path_info = req.path_info
111
+ routes = filter_routes(path_info).concat custom_routes.find_all { |r|
112
+ r.path.match?(path_info)
110
113
  }
111
114
 
112
- routes =
113
- if req.head?
114
- match_head_routes(routes, req)
115
- else
116
- match_routes(routes, req)
117
- end
115
+ if req.head?
116
+ routes = match_head_routes(routes, req)
117
+ else
118
+ routes.select! { |r| r.matches?(req) }
119
+ end
118
120
 
119
121
  routes.sort_by!(&:precedence)
120
122
 
121
123
  routes.map! { |r|
122
- match_data = r.path.match(req.path_info)
124
+ match_data = r.path.match(path_info)
123
125
  path_parameters = {}
124
- match_data.names.zip(match_data.captures) { |name, val|
126
+ match_data.names.each_with_index { |name, i|
127
+ val = match_data[i + 1]
125
128
  path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
126
129
  }
127
130
  [match_data, path_parameters, r]
@@ -129,24 +132,17 @@ module ActionDispatch
129
132
  end
130
133
 
131
134
  def match_head_routes(routes, req)
132
- verb_specific_routes = routes.select(&:requires_matching_verb?)
133
- head_routes = match_routes(verb_specific_routes, req)
134
-
135
- if head_routes.empty?
136
- begin
137
- req.request_method = "GET"
138
- match_routes(routes, req)
139
- ensure
140
- req.request_method = "HEAD"
141
- end
142
- else
143
- head_routes
135
+ head_routes = routes.select { |r| r.requires_matching_verb? && r.matches?(req) }
136
+ return head_routes unless head_routes.empty?
137
+
138
+ begin
139
+ req.request_method = "GET"
140
+ routes.select! { |r| r.matches?(req) }
141
+ routes
142
+ ensure
143
+ req.request_method = "HEAD"
144
144
  end
145
145
  end
146
-
147
- def match_routes(routes, req)
148
- routes.select { |r| r.matches?(req) }
149
- end
150
146
  end
151
147
  end
152
148
  end
@@ -3,5 +3,3 @@
3
3
  require "action_dispatch/journey/router"
4
4
  require "action_dispatch/journey/gtg/builder"
5
5
  require "action_dispatch/journey/gtg/simulator"
6
- require "action_dispatch/journey/nfa/builder"
7
- require "action_dispatch/journey/nfa/simulator"
@@ -24,7 +24,7 @@ module ActionDispatch
24
24
 
25
25
  private
26
26
  def actionable_request?(request)
27
- request.get_header("action_dispatch.show_detailed_exceptions") && request.post? && request.path == endpoint
27
+ request.get_header("action_dispatch.show_detailed_exceptions") && request.post? && request.path == endpoint
28
28
  end
29
29
 
30
30
  def redirect_to(location)
@@ -69,6 +69,10 @@ module ActionDispatch
69
69
  get_header Cookies::COOKIES_SERIALIZER
70
70
  end
71
71
 
72
+ def cookies_same_site_protection
73
+ get_header(Cookies::COOKIES_SAME_SITE_PROTECTION) || Proc.new { }
74
+ end
75
+
72
76
  def cookies_digest
73
77
  get_header Cookies::COOKIES_DIGEST
74
78
  end
@@ -84,11 +88,10 @@ module ActionDispatch
84
88
  # :startdoc:
85
89
  end
86
90
 
87
- # \Cookies are read and written through ActionController#cookies.
91
+ # Read and write data to cookies through ActionController#cookies.
88
92
  #
89
- # The cookies being read are the ones received along with the request, the cookies
90
- # being written will be sent out with the response. Reading a cookie does not get
91
- # the cookie object itself back, just the value it holds.
93
+ # When reading cookie data, the data is read from the HTTP request header, Cookie.
94
+ # When writing cookie data, the data is sent out in the HTTP response header, Set-Cookie.
92
95
  #
93
96
  # Examples of writing:
94
97
  #
@@ -150,8 +153,10 @@ module ActionDispatch
150
153
  # * <tt>:domain</tt> - The domain for which this cookie applies so you can
151
154
  # restrict to the domain level. If you use a schema like www.example.com
152
155
  # and want to share session with user.example.com set <tt>:domain</tt>
153
- # to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
154
- # <tt>:all</tt> or <tt>Array</tt> again when deleting cookies.
156
+ # to <tt>:all</tt>. To support multiple domains, provide an array, and
157
+ # the first domain matching <tt>request.host</tt> will be used. Make
158
+ # sure to specify the <tt>:domain</tt> option with <tt>:all</tt> or
159
+ # <tt>Array</tt> again when deleting cookies.
155
160
  #
156
161
  # domain: nil # Does not set cookie domain. (default)
157
162
  # domain: :all # Allow the cookie for the top most level
@@ -181,6 +186,7 @@ module ActionDispatch
181
186
  COOKIES_SERIALIZER = "action_dispatch.cookies_serializer"
182
187
  COOKIES_DIGEST = "action_dispatch.cookies_digest"
183
188
  COOKIES_ROTATIONS = "action_dispatch.cookies_rotations"
189
+ COOKIES_SAME_SITE_PROTECTION = "action_dispatch.cookies_same_site_protection"
184
190
  USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata"
185
191
 
186
192
  # Cookies can typically store 4096 bytes.
@@ -259,6 +265,12 @@ module ActionDispatch
259
265
  request.use_authenticated_cookie_encryption
260
266
  end
261
267
 
268
+ def prepare_upgrade_legacy_hmac_aes_cbc_cookies?
269
+ request.secret_key_base.present? &&
270
+ request.authenticated_encrypted_cookie_salt.present? &&
271
+ !request.use_authenticated_cookie_encryption
272
+ end
273
+
262
274
  def encrypted_cookie_cipher
263
275
  request.encrypted_cookie_cipher || "aes-256-gcm"
264
276
  end
@@ -286,9 +298,9 @@ module ActionDispatch
286
298
  DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
287
299
 
288
300
  def self.build(req, cookies)
289
- new(req).tap do |hash|
290
- hash.update(cookies)
291
- end
301
+ jar = new(req)
302
+ jar.update(cookies)
303
+ jar
292
304
  end
293
305
 
294
306
  attr_reader :request
@@ -346,28 +358,6 @@ module ActionDispatch
346
358
  @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
347
359
  end
348
360
 
349
- def handle_options(options) # :nodoc:
350
- if options[:expires].respond_to?(:from_now)
351
- options[:expires] = options[:expires].from_now
352
- end
353
-
354
- options[:path] ||= "/"
355
-
356
- if options[:domain] == :all || options[:domain] == "all"
357
- # If there is a provided tld length then we use it otherwise default domain regexp.
358
- domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
359
-
360
- # If host is not ip and matches domain regexp.
361
- # (ip confirms to domain regexp so we explicitly check for ip)
362
- options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
363
- ".#{$&}"
364
- end
365
- elsif options[:domain].is_a? Array
366
- # If host matches one of the supplied domains without a dot in front of it.
367
- options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") }
368
- end
369
- end
370
-
371
361
  # Sets the cookie named +name+. The second argument may be the cookie's
372
362
  # value or a hash of options as documented above.
373
363
  def []=(name, options)
@@ -447,6 +437,34 @@ module ActionDispatch
447
437
  def write_cookie?(cookie)
448
438
  request.ssl? || !cookie[:secure] || always_write_cookie
449
439
  end
440
+
441
+ def handle_options(options)
442
+ if options[:expires].respond_to?(:from_now)
443
+ options[:expires] = options[:expires].from_now
444
+ end
445
+
446
+ options[:path] ||= "/"
447
+
448
+ cookies_same_site_protection = request.cookies_same_site_protection
449
+ options[:same_site] ||= cookies_same_site_protection.call(request)
450
+
451
+ if options[:domain] == :all || options[:domain] == "all"
452
+ # If there is a provided tld length then we use it otherwise default domain regexp.
453
+ domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
454
+
455
+ # If host is not ip and matches domain regexp.
456
+ # (ip confirms to domain regexp so we explicitly check for ip)
457
+ options[:domain] = if !request.host.match?(/^[\d.]+$/) && (request.host =~ domain_regexp)
458
+ ".#{$&}"
459
+ end
460
+ elsif options[:domain].is_a? Array
461
+ # If host matches one of the supplied domains.
462
+ options[:domain] = options[:domain].find do |domain|
463
+ domain = domain.delete_prefix(".")
464
+ request.host == domain || request.host.end_with?(".#{domain}")
465
+ end
466
+ end
467
+ end
450
468
  end
451
469
 
452
470
  class AbstractCookieJar # :nodoc:
@@ -508,6 +526,18 @@ module ActionDispatch
508
526
  end
509
527
  end
510
528
 
529
+ class MarshalWithJsonFallback # :nodoc:
530
+ def self.load(value)
531
+ Marshal.load(value)
532
+ rescue TypeError => e
533
+ ActiveSupport::JSON.decode(value) rescue raise e
534
+ end
535
+
536
+ def self.dump(value)
537
+ Marshal.dump(value)
538
+ end
539
+ end
540
+
511
541
  class JsonSerializer # :nodoc:
512
542
  def self.load(value)
513
543
  ActiveSupport::JSON.decode(value)
@@ -555,7 +585,7 @@ module ActionDispatch
555
585
  serializer = request.cookies_serializer || :marshal
556
586
  case serializer
557
587
  when :marshal
558
- Marshal
588
+ MarshalWithJsonFallback
559
589
  when :json, :hybrid
560
590
  JsonSerializer
561
591
  else
@@ -625,6 +655,11 @@ module ActionDispatch
625
655
  sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
626
656
 
627
657
  @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
658
+ elsif prepare_upgrade_legacy_hmac_aes_cbc_cookies?
659
+ future_cipher = encrypted_cookie_cipher
660
+ secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(future_cipher))
661
+
662
+ @encryptor.rotate(secret, nil, cipher: future_cipher, serializer: SERIALIZER)
628
663
  end
629
664
  end
630
665
 
@@ -4,10 +4,7 @@ require "action_dispatch/http/request"
4
4
  require "action_dispatch/middleware/exception_wrapper"
5
5
  require "action_dispatch/routing/inspector"
6
6
 
7
- require "active_support/actionable_error"
8
-
9
7
  require "action_view"
10
- require "action_view/base"
11
8
 
12
9
  module ActionDispatch
13
10
  # This middleware is responsible for logging exceptions and
@@ -140,20 +137,16 @@ module ActionDispatch
140
137
  return unless logger
141
138
 
142
139
  exception = wrapper.exception
140
+ trace = wrapper.exception_trace
143
141
 
144
- trace = wrapper.application_trace
145
- trace = wrapper.framework_trace if trace.empty?
146
-
147
- ActiveSupport::Deprecation.silence do
148
- message = []
149
- message << " "
150
- message << "#{exception.class} (#{exception.message}):"
151
- message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
152
- message << " "
153
- message.concat(trace)
142
+ message = []
143
+ message << " "
144
+ message << "#{exception.class} (#{exception.message}):"
145
+ message.concat(exception.annotated_source_code) if exception.respond_to?(:annotated_source_code)
146
+ message << " "
147
+ message.concat(trace)
154
148
 
155
- log_array(logger, message)
156
- end
149
+ log_array(logger, message)
157
150
  end
158
151
 
159
152
  def log_array(logger, array)
@@ -12,7 +12,7 @@ module ActionDispatch
12
12
  def initialize(assigns)
13
13
  paths = [RESCUES_TEMPLATE_PATH]
14
14
  lookup_context = ActionView::LookupContext.new(paths)
15
- super(lookup_context, assigns)
15
+ super(lookup_context, assigns, nil)
16
16
  end
17
17
 
18
18
  def compiled_method_container