actionpack 4.1.7 → 4.2.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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +311 -527
  3. data/README.rdoc +7 -2
  4. data/lib/abstract_controller/base.rb +16 -6
  5. data/lib/abstract_controller/callbacks.rb +28 -51
  6. data/lib/abstract_controller/helpers.rb +11 -4
  7. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  8. data/lib/abstract_controller/url_for.rb +1 -1
  9. data/lib/action_controller/base.rb +2 -1
  10. data/lib/action_controller/caching/fragments.rb +7 -1
  11. data/lib/action_controller/caching.rb +1 -1
  12. data/lib/action_controller/log_subscriber.rb +26 -26
  13. data/lib/action_controller/metal/conditional_get.rb +37 -12
  14. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  15. data/lib/action_controller/metal/exceptions.rb +1 -1
  16. data/lib/action_controller/metal/force_ssl.rb +1 -1
  17. data/lib/action_controller/metal/head.rb +7 -3
  18. data/lib/action_controller/metal/http_authentication.rb +14 -9
  19. data/lib/action_controller/metal/instrumentation.rb +8 -5
  20. data/lib/action_controller/metal/live.rb +57 -6
  21. data/lib/action_controller/metal/mime_responds.rb +23 -246
  22. data/lib/action_controller/metal/params_wrapper.rb +2 -2
  23. data/lib/action_controller/metal/rack_delegation.rb +1 -1
  24. data/lib/action_controller/metal/redirecting.rb +14 -8
  25. data/lib/action_controller/metal/renderers.rb +30 -10
  26. data/lib/action_controller/metal/rendering.rb +2 -6
  27. data/lib/action_controller/metal/request_forgery_protection.rb +78 -7
  28. data/lib/action_controller/metal/streaming.rb +1 -1
  29. data/lib/action_controller/metal/strong_parameters.rb +125 -12
  30. data/lib/action_controller/metal/url_for.rb +11 -12
  31. data/lib/action_controller/metal.rb +12 -11
  32. data/lib/action_controller/model_naming.rb +1 -1
  33. data/lib/action_controller/railtie.rb +4 -0
  34. data/lib/action_controller/test_case.rb +112 -75
  35. data/lib/action_controller.rb +1 -1
  36. data/lib/action_dispatch/http/cache.rb +5 -4
  37. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  38. data/lib/action_dispatch/http/headers.rb +43 -9
  39. data/lib/action_dispatch/http/mime_negotiation.rb +10 -3
  40. data/lib/action_dispatch/http/mime_type.rb +2 -2
  41. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  42. data/lib/action_dispatch/http/parameters.rb +11 -26
  43. data/lib/action_dispatch/http/request.rb +37 -11
  44. data/lib/action_dispatch/http/response.rb +70 -18
  45. data/lib/action_dispatch/http/upload.rb +3 -8
  46. data/lib/action_dispatch/http/url.rb +88 -69
  47. data/lib/action_dispatch/journey/formatter.rb +33 -17
  48. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  49. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  50. data/lib/action_dispatch/journey/gtg/transition_table.rb +20 -28
  51. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  52. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  53. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  54. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  55. data/lib/action_dispatch/journey/parser.rb +52 -60
  56. data/lib/action_dispatch/journey/parser.y +11 -10
  57. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  58. data/lib/action_dispatch/journey/route.rb +3 -18
  59. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  60. data/lib/action_dispatch/journey/router.rb +53 -77
  61. data/lib/action_dispatch/journey/scanner.rb +5 -5
  62. data/lib/action_dispatch/journey/visitors.rb +81 -92
  63. data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
  64. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  65. data/lib/action_dispatch/middleware/callbacks.rb +1 -1
  66. data/lib/action_dispatch/middleware/cookies.rb +29 -29
  67. data/lib/action_dispatch/middleware/debug_exceptions.rb +15 -4
  68. data/lib/action_dispatch/middleware/exception_wrapper.rb +50 -18
  69. data/lib/action_dispatch/middleware/flash.rb +13 -7
  70. data/lib/action_dispatch/middleware/params_parser.rb +1 -1
  71. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
  72. data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
  73. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  74. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  75. data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
  76. data/lib/action_dispatch/middleware/static.rb +66 -37
  77. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
  78. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +37 -9
  79. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
  80. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
  81. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  82. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  83. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -0
  84. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +2 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
  86. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
  87. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
  88. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  89. data/lib/action_dispatch/routing/inspector.rb +5 -12
  90. data/lib/action_dispatch/routing/mapper.rb +410 -281
  91. data/lib/action_dispatch/routing/polymorphic_routes.rb +191 -79
  92. data/lib/action_dispatch/routing/redirection.rb +10 -12
  93. data/lib/action_dispatch/routing/route_set.rb +297 -168
  94. data/lib/action_dispatch/routing/url_for.rb +15 -4
  95. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  96. data/lib/action_dispatch/testing/assertions/response.rb +2 -7
  97. data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
  98. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  99. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  100. data/lib/action_dispatch/testing/assertions.rb +11 -7
  101. data/lib/action_dispatch/testing/integration.rb +24 -19
  102. data/lib/action_dispatch/testing/test_request.rb +1 -1
  103. data/lib/action_dispatch/testing/test_response.rb +7 -0
  104. data/lib/action_pack/gem_version.rb +3 -3
  105. metadata +55 -13
  106. data/lib/action_controller/metal/responder.rb +0 -297
@@ -20,62 +20,32 @@ module ActionDispatch
20
20
  # :nodoc:
21
21
  VERSION = '2.0.0'
22
22
 
23
- class NullReq # :nodoc:
24
- attr_reader :env
25
- def initialize(env)
26
- @env = env
27
- end
28
-
29
- def request_method
30
- env['REQUEST_METHOD']
31
- end
32
-
33
- def path_info
34
- env['PATH_INFO']
35
- end
36
-
37
- def ip
38
- env['REMOTE_ADDR']
39
- end
40
-
41
- def [](k)
42
- env[k]
43
- end
44
- end
45
-
46
- attr_reader :request_class, :formatter
47
23
  attr_accessor :routes
48
24
 
49
- def initialize(routes, options)
50
- @options = options
51
- @params_key = options[:parameters_key]
52
- @request_class = options[:request_class] || NullReq
53
- @routes = routes
25
+ def initialize(routes)
26
+ @routes = routes
54
27
  end
55
28
 
56
- def call(env)
57
- env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO'])
58
-
59
- find_routes(env).each do |match, parameters, route|
60
- script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
61
- 'PATH_INFO',
62
- @params_key)
29
+ def serve(req)
30
+ find_routes(req).each do |match, parameters, route|
31
+ set_params = req.path_parameters
32
+ path_info = req.path_info
33
+ script_name = req.script_name
63
34
 
64
35
  unless route.path.anchored
65
- env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/')
66
- path_info = match.post_match
67
- env['PATH_INFO'] = path_info
68
- env['PATH_INFO'] = "/" + path_info unless path_info.start_with? "/"
36
+ req.script_name = (script_name.to_s + match.to_s).chomp('/')
37
+ req.path_info = match.post_match
38
+ req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
69
39
  end
70
40
 
71
- env[@params_key] = (set_params || {}).merge parameters
41
+ req.path_parameters = set_params.merge parameters
72
42
 
73
- status, headers, body = route.app.call(env)
43
+ status, headers, body = route.app.serve(req)
74
44
 
75
45
  if 'pass' == headers['X-Cascade']
76
- env['SCRIPT_NAME'] = script_name
77
- env['PATH_INFO'] = path_info
78
- env[@params_key] = set_params
46
+ req.script_name = script_name
47
+ req.path_info = path_info
48
+ req.path_parameters = set_params
79
49
  next
80
50
  end
81
51
 
@@ -85,14 +55,14 @@ module ActionDispatch
85
55
  return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
86
56
  end
87
57
 
88
- def recognize(req)
89
- find_routes(req.env).each do |match, parameters, route|
58
+ def recognize(rails_req)
59
+ find_routes(rails_req).each do |match, parameters, route|
90
60
  unless route.path.anchored
91
- req.env['SCRIPT_NAME'] = match.to_s
92
- req.env['PATH_INFO'] = match.post_match.sub(/^([^\/])/, '/\1')
61
+ rails_req.script_name = match.to_s
62
+ rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1')
93
63
  end
94
64
 
95
- yield(route, nil, parameters)
65
+ yield(route, parameters)
96
66
  end
97
67
  end
98
68
 
@@ -123,45 +93,51 @@ module ActionDispatch
123
93
 
124
94
  def filter_routes(path)
125
95
  return [] unless ast
126
- data = simulator.match(path)
127
- data ? data.memos : []
96
+ simulator.memos(path) { [] }
128
97
  end
129
98
 
130
- def find_routes env
131
- req = request_class.new(env)
132
-
99
+ def find_routes req
133
100
  routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
134
101
  r.path.match(req.path_info)
135
102
  }
136
- routes.concat get_routes_as_head(routes)
137
103
 
138
- routes.sort_by!(&:precedence).select! { |r| r.matches?(req) }
104
+ routes =
105
+ if req.request_method == "HEAD"
106
+ match_head_routes(routes, req)
107
+ else
108
+ match_routes(routes, req)
109
+ end
110
+
111
+ routes.sort_by!(&:precedence)
139
112
 
140
113
  routes.map! { |r|
141
114
  match_data = r.path.match(req.path_info)
142
- match_names = match_data.names.map { |n| n.to_sym }
143
- match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) }
144
- info = Hash[match_names.zip(match_values).find_all { |_, y| y }]
145
-
146
- [match_data, r.defaults.merge(info), r]
115
+ path_parameters = r.defaults.dup
116
+ match_data.names.zip(match_data.captures) { |name,val|
117
+ path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
118
+ }
119
+ [match_data, path_parameters, r]
147
120
  }
148
121
  end
149
122
 
150
- def get_routes_as_head(routes)
151
- precedence = (routes.map(&:precedence).max || 0) + 1
152
- routes = routes.select { |r|
153
- r.verb === "GET" && !(r.verb === "HEAD")
154
- }.map! { |r|
155
- Route.new(r.name,
156
- r.app,
157
- r.path,
158
- r.conditions.merge(request_method: "HEAD"),
159
- r.defaults).tap do |route|
160
- route.precedence = r.precedence + precedence
161
- end
162
- }
163
- routes.flatten!
164
- routes
123
+ def match_head_routes(routes, req)
124
+ routes.delete_if { |route| route.verb == // }
125
+ head_routes = match_routes(routes, req)
126
+
127
+ if head_routes.empty?
128
+ begin
129
+ req.request_method = "GET"
130
+ match_routes(routes, req)
131
+ ensure
132
+ req.request_method = "HEAD"
133
+ end
134
+ else
135
+ head_routes
136
+ end
137
+ end
138
+
139
+ def match_routes(routes, req)
140
+ routes.select { |r| r.matches?(req) }
165
141
  end
166
142
  end
167
143
  end
@@ -39,18 +39,18 @@ module ActionDispatch
39
39
  [:SLASH, text]
40
40
  when text = @ss.scan(/\*\w+/)
41
41
  [:STAR, text]
42
- when text = @ss.scan(/\(/)
42
+ when text = @ss.scan(/(?<!\\)\(/)
43
43
  [:LPAREN, text]
44
- when text = @ss.scan(/\)/)
44
+ when text = @ss.scan(/(?<!\\)\)/)
45
45
  [:RPAREN, text]
46
46
  when text = @ss.scan(/\|/)
47
47
  [:OR, text]
48
48
  when text = @ss.scan(/\./)
49
49
  [:DOT, text]
50
- when text = @ss.scan(/:\w+/)
50
+ when text = @ss.scan(/(?<!\\):\w+/)
51
51
  [:SYMBOL, text]
52
- when text = @ss.scan(/[\w%\-~]+/)
53
- [:LITERAL, text]
52
+ when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\:|\\\(|\\\))+/)
53
+ [:LITERAL, text.tr('\\', '')]
54
54
  # any char
55
55
  when text = @ss.scan(/./)
56
56
  [:LITERAL, text]
@@ -1,14 +1,57 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'thread_safe'
4
-
5
3
  module ActionDispatch
6
4
  module Journey # :nodoc:
5
+ class Format
6
+ ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) }
7
+ ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) }
8
+
9
+ class Parameter < Struct.new(:name, :escaper)
10
+ def escape(value); escaper.call value; end
11
+ end
12
+
13
+ def self.required_path(symbol)
14
+ Parameter.new symbol, ESCAPE_PATH
15
+ end
16
+
17
+ def self.required_segment(symbol)
18
+ Parameter.new symbol, ESCAPE_SEGMENT
19
+ end
20
+
21
+ def initialize(parts)
22
+ @parts = parts
23
+ @children = []
24
+ @parameters = []
25
+
26
+ parts.each_with_index do |object,i|
27
+ case object
28
+ when Journey::Format
29
+ @children << i
30
+ when Parameter
31
+ @parameters << i
32
+ end
33
+ end
34
+ end
35
+
36
+ def evaluate(hash)
37
+ parts = @parts.dup
38
+
39
+ @parameters.each do |index|
40
+ param = parts[index]
41
+ value = hash[param.name]
42
+ return ''.freeze unless value
43
+ parts[index] = param.escape value
44
+ end
45
+
46
+ @children.each { |index| parts[index] = parts[index].evaluate(hash) }
47
+
48
+ parts.join
49
+ end
50
+ end
51
+
7
52
  module Visitors # :nodoc:
8
53
  class Visitor # :nodoc:
9
- DISPATCH_CACHE = ThreadSafe::Cache.new { |h,k|
10
- h[k] = :"visit_#{k}"
11
- }
54
+ DISPATCH_CACHE = {}
12
55
 
13
56
  def accept(node)
14
57
  visit(node)
@@ -38,11 +81,41 @@ module ActionDispatch
38
81
  def visit_STAR(n); unary(n); end
39
82
 
40
83
  def terminal(node); end
41
- %w{ LITERAL SYMBOL SLASH DOT }.each do |t|
42
- class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__
84
+ def visit_LITERAL(n); terminal(n); end
85
+ def visit_SYMBOL(n); terminal(n); end
86
+ def visit_SLASH(n); terminal(n); end
87
+ def visit_DOT(n); terminal(n); end
88
+
89
+ private_instance_methods(false).each do |pim|
90
+ next unless pim =~ /^visit_(.*)$/
91
+ DISPATCH_CACHE[$1.to_sym] = pim
43
92
  end
44
93
  end
45
94
 
95
+ class FormatBuilder < Visitor # :nodoc:
96
+ def accept(node); Journey::Format.new(super); end
97
+ def terminal(node); [node.left]; end
98
+
99
+ def binary(node)
100
+ visit(node.left) + visit(node.right)
101
+ end
102
+
103
+ def visit_GROUP(n); [Journey::Format.new(unary(n))]; end
104
+
105
+ def visit_STAR(n)
106
+ [Journey::Format.required_path(n.left.to_sym)]
107
+ end
108
+
109
+ def visit_SYMBOL(n)
110
+ symbol = n.to_sym
111
+ if symbol == :controller
112
+ [Journey::Format.required_path(symbol)]
113
+ else
114
+ [Journey::Format.required_segment(symbol)]
115
+ end
116
+ end
117
+ end
118
+
46
119
  # Loop through the requirements AST
47
120
  class Each < Visitor # :nodoc:
48
121
  attr_reader :block
@@ -52,8 +125,8 @@ module ActionDispatch
52
125
  end
53
126
 
54
127
  def visit(node)
55
- super
56
128
  block.call(node)
129
+ super
57
130
  end
58
131
  end
59
132
 
@@ -77,90 +150,6 @@ module ActionDispatch
77
150
  end
78
151
  end
79
152
 
80
- class OptimizedPath < Visitor # :nodoc:
81
- def accept(node)
82
- Array(visit(node))
83
- end
84
-
85
- private
86
-
87
- def visit_CAT(node)
88
- [visit(node.left), visit(node.right)].flatten
89
- end
90
-
91
- def visit_SYMBOL(node)
92
- node.left[1..-1].to_sym
93
- end
94
-
95
- def visit_STAR(node)
96
- visit(node.left)
97
- end
98
-
99
- def visit_GROUP(node)
100
- []
101
- end
102
-
103
- %w{ LITERAL SLASH DOT }.each do |t|
104
- class_eval %{ def visit_#{t}(n); n.left; end }, __FILE__, __LINE__
105
- end
106
- end
107
-
108
- # Used for formatting urls (url_for)
109
- class Formatter < Visitor # :nodoc:
110
- attr_reader :options
111
-
112
- def initialize(options)
113
- @options = options
114
- end
115
-
116
- private
117
- def escape_path(value)
118
- Router::Utils.escape_path(value)
119
- end
120
-
121
- def escape_segment(value)
122
- Router::Utils.escape_segment(value)
123
- end
124
-
125
- def visit(node, optional = false)
126
- case node.type
127
- when :LITERAL, :SLASH, :DOT
128
- node.left
129
- when :STAR
130
- visit_STAR(node.left)
131
- when :GROUP
132
- visit(node.left, true)
133
- when :CAT
134
- visit_CAT(node, optional)
135
- when :SYMBOL
136
- visit_SYMBOL(node, node.to_sym)
137
- end
138
- end
139
-
140
- def visit_CAT(node, optional)
141
- left = visit(node.left, optional)
142
- right = visit(node.right, optional)
143
-
144
- if optional && !(right && left)
145
- ""
146
- else
147
- [left, right].join
148
- end
149
- end
150
-
151
- def visit_STAR(node)
152
- if value = options[node.to_sym]
153
- escape_path(value)
154
- end
155
- end
156
-
157
- def visit_SYMBOL(node, name)
158
- if value = options[name]
159
- name == :controller ? escape_path(value) : escape_segment(value)
160
- end
161
- end
162
- end
163
-
164
153
  class Dot < Visitor # :nodoc:
165
154
  def initialize
166
155
  @nodes = []
@@ -16,10 +16,6 @@ h2 {
16
16
  font-size: 0.5em;
17
17
  }
18
18
 
19
- div#chart-2 {
20
- height: 350px;
21
- }
22
-
23
19
  .clearfix {display: inline-block; }
24
20
  .input { overflow: show;}
25
21
  .instruction { color: #666; padding: 0 30px 20px; font-size: 0.9em}
@@ -2,13 +2,13 @@
2
2
  <html>
3
3
  <head>
4
4
  <title><%= title %></title>
5
- <link rel="stylesheet" href="https://raw.github.com/gist/1706081/af944401f75ea20515a02ddb3fb43d23ecb8c662/reset.css" type="text/css">
5
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.css" type="text/css">
6
6
  <style>
7
7
  <% stylesheets.each do |style| %>
8
8
  <%= style %>
9
9
  <% end %>
10
10
  </style>
11
- <script src="https://raw.github.com/gist/1706081/df464722a05c3c2bec450b7b5c8240d9c31fa52d/d3.min.js" type="text/javascript"></script>
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js" type="text/javascript"></script>
12
12
  </head>
13
13
  <body>
14
14
  <div id="wrapper">
@@ -1,6 +1,6 @@
1
1
 
2
2
  module ActionDispatch
3
- # Provide callbacks to be executed before and after the request dispatch.
3
+ # Provides callbacks to be executed before and after dispatching the request.
4
4
  class Callbacks
5
5
  include ActiveSupport::Callbacks
6
6
 
@@ -3,6 +3,7 @@ require 'active_support/core_ext/module/attribute_accessors'
3
3
  require 'active_support/core_ext/object/blank'
4
4
  require 'active_support/key_generator'
5
5
  require 'active_support/message_verifier'
6
+ require 'active_support/json'
6
7
 
7
8
  module ActionDispatch
8
9
  class Request < Rack::Request
@@ -70,11 +71,13 @@ module ActionDispatch
70
71
  # restrict to the domain level. If you use a schema like www.example.com
71
72
  # and want to share session with user.example.com set <tt>:domain</tt>
72
73
  # to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
73
- # <tt>:all</tt> again when deleting cookies.
74
+ # <tt>:all</tt> or <tt>Array</tt> again when deleting cookies.
74
75
  #
75
76
  # domain: nil # Does not sets cookie domain. (default)
76
77
  # domain: :all # Allow the cookie for the top most level
77
- # domain and subdomains.
78
+ # # domain and subdomains.
79
+ # domain: %w(.example.com .example.org) # Allow the cookie
80
+ # # for concrete domain names.
78
81
  #
79
82
  # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
80
83
  # * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
@@ -90,6 +93,7 @@ module ActionDispatch
90
93
  SECRET_TOKEN = "action_dispatch.secret_token".freeze
91
94
  SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
92
95
  COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
96
+ COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
93
97
 
94
98
  # Cookies can typically store 4096 bytes.
95
99
  MAX_COOKIE_SIZE = 4096
@@ -118,7 +122,7 @@ module ActionDispatch
118
122
  # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
119
123
  # cookie was tampered with by the user (or a 3rd party), nil will be returned.
120
124
  #
121
- # If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
125
+ # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
122
126
  # legacy cookies signed with the old key generator will be transparently upgraded.
123
127
  #
124
128
  # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
@@ -141,7 +145,7 @@ module ActionDispatch
141
145
  # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
142
146
  # If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
143
147
  #
144
- # If +secrets.secret_key_base+ and +config.secret_token+ (deprecated) are both set,
148
+ # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
145
149
  # legacy cookies signed with the old key generator will be transparently upgraded.
146
150
  #
147
151
  # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
@@ -173,10 +177,14 @@ module ActionDispatch
173
177
  end
174
178
  end
175
179
 
180
+ # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
181
+ # to the Message{Encryptor,Verifier} allows us to handle the
182
+ # (de)serialization step within the cookie jar, which gives us the
183
+ # opportunity to detect and migrate legacy cookies.
176
184
  module VerifyAndUpgradeLegacySignedMessage
177
185
  def initialize(*args)
178
186
  super
179
- @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: NullSerializer)
187
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: ActiveSupport::MessageEncryptor::NullSerializer)
180
188
  end
181
189
 
182
190
  def verify_and_upgrade_legacy_signed_message(name, signed_message)
@@ -212,7 +220,8 @@ module ActionDispatch
212
220
  secret_token: env[SECRET_TOKEN],
213
221
  secret_key_base: env[SECRET_KEY_BASE],
214
222
  upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
215
- serializer: env[COOKIES_SERIALIZER]
223
+ serializer: env[COOKIES_SERIALIZER],
224
+ digest: env[COOKIES_DIGEST]
216
225
  }
217
226
  end
218
227
 
@@ -289,8 +298,8 @@ module ActionDispatch
289
298
  end
290
299
  end
291
300
 
292
- # Sets the cookie named +name+. The second argument may be the very cookie
293
- # value, or a hash of options as documented above.
301
+ # Sets the cookie named +name+. The second argument may be the cookie's
302
+ # value or a hash of options as documented above.
294
303
  def []=(name, options)
295
304
  if options.is_a?(Hash)
296
305
  options.symbolize_keys!
@@ -385,24 +394,11 @@ module ActionDispatch
385
394
 
386
395
  class JsonSerializer
387
396
  def self.load(value)
388
- JSON.parse(value, quirks_mode: true)
397
+ ActiveSupport::JSON.decode(value)
389
398
  end
390
399
 
391
400
  def self.dump(value)
392
- JSON.generate(value, quirks_mode: true)
393
- end
394
- end
395
-
396
- # Passing the NullSerializer downstream to the Message{Encryptor,Verifier}
397
- # allows us to handle the (de)serialization step within the cookie jar,
398
- # which gives us the opportunity to detect and migrate legacy cookies.
399
- class NullSerializer
400
- def self.load(value)
401
- value
402
- end
403
-
404
- def self.dump(value)
405
- value
401
+ ActiveSupport::JSON.encode(value)
406
402
  end
407
403
  end
408
404
 
@@ -441,6 +437,10 @@ module ActionDispatch
441
437
  serializer
442
438
  end
443
439
  end
440
+
441
+ def digest
442
+ @options[:digest] || 'SHA1'
443
+ end
444
444
  end
445
445
 
446
446
  class SignedCookieJar #:nodoc:
@@ -451,7 +451,7 @@ module ActionDispatch
451
451
  @parent_jar = parent_jar
452
452
  @options = options
453
453
  secret = key_generator.generate_key(@options[:signed_cookie_salt])
454
- @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: NullSerializer)
454
+ @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
455
455
  end
456
456
 
457
457
  def [](name)
@@ -468,7 +468,7 @@ module ActionDispatch
468
468
  options = { :value => @verifier.generate(serialize(name, options)) }
469
469
  end
470
470
 
471
- raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
471
+ raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
472
472
  @parent_jar[name] = options
473
473
  end
474
474
 
@@ -481,7 +481,7 @@ module ActionDispatch
481
481
  end
482
482
 
483
483
  # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
484
- # config.secret_token and secrets.secret_key_base are both set. It reads
484
+ # secrets.secret_token and secrets.secret_key_base are both set. It reads
485
485
  # legacy cookies signed with the old dummy key generator and re-saves
486
486
  # them using the new key generator to provide a smooth upgrade path.
487
487
  class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
@@ -508,7 +508,7 @@ module ActionDispatch
508
508
  @options = options
509
509
  secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
510
510
  sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
511
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: NullSerializer)
511
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
512
512
  end
513
513
 
514
514
  def [](name)
@@ -526,7 +526,7 @@ module ActionDispatch
526
526
 
527
527
  options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value]))
528
528
 
529
- raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
529
+ raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
530
530
  @parent_jar[name] = options
531
531
  end
532
532
 
@@ -539,7 +539,7 @@ module ActionDispatch
539
539
  end
540
540
 
541
541
  # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
542
- # instead of EncryptedCookieJar if config.secret_token and secrets.secret_key_base
542
+ # instead of EncryptedCookieJar if secrets.secret_token and secrets.secret_key_base
543
543
  # are both set. It reads legacy cookies signed with the old dummy key generator and
544
544
  # encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
545
545
  class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc: