actionpack 4.1.0.beta2 → 4.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -10
  3. data/MIT-LICENSE +1 -1
  4. data/lib/abstract_controller/rendering.rb +4 -3
  5. data/lib/action_controller/log_subscriber.rb +9 -0
  6. data/lib/action_controller/metal/head.rb +1 -1
  7. data/lib/action_controller/metal/live.rb +3 -1
  8. data/lib/action_controller/metal/mime_responds.rb +66 -27
  9. data/lib/action_controller/metal/params_wrapper.rb +11 -4
  10. data/lib/action_controller/metal/rack_delegation.rb +2 -2
  11. data/lib/action_controller/metal/renderers.rb +1 -1
  12. data/lib/action_controller/metal/rendering.rb +38 -8
  13. data/lib/action_controller/metal/strong_parameters.rb +26 -14
  14. data/lib/action_controller/railtie.rb +1 -0
  15. data/lib/action_controller/test_case.rb +3 -0
  16. data/lib/action_dispatch.rb +5 -7
  17. data/lib/action_dispatch/http/filter_redirect.rb +5 -4
  18. data/lib/action_dispatch/http/mime_negotiation.rb +5 -3
  19. data/lib/action_dispatch/http/mime_type.rb +1 -1
  20. data/lib/action_dispatch/http/response.rb +14 -2
  21. data/lib/action_dispatch/journey/formatter.rb +2 -2
  22. data/lib/action_dispatch/journey/router.rb +1 -7
  23. data/lib/action_dispatch/journey/visitors.rb +24 -4
  24. data/lib/action_dispatch/middleware/cookies.rb +85 -20
  25. data/lib/action_dispatch/middleware/flash.rb +20 -7
  26. data/lib/action_dispatch/middleware/reloader.rb +11 -2
  27. data/lib/action_dispatch/middleware/remote_ip.rb +2 -2
  28. data/lib/action_dispatch/middleware/static.rb +3 -3
  29. data/lib/action_dispatch/railtie.rb +2 -0
  30. data/lib/action_dispatch/request/utils.rb +15 -4
  31. data/lib/action_dispatch/routing/inspector.rb +4 -4
  32. data/lib/action_dispatch/routing/mapper.rb +27 -9
  33. data/lib/action_dispatch/routing/redirection.rb +18 -8
  34. data/lib/action_dispatch/routing/route_set.rb +24 -30
  35. data/lib/action_dispatch/testing/assertions/routing.rb +1 -1
  36. data/lib/action_dispatch/testing/integration.rb +2 -2
  37. data/lib/action_pack.rb +1 -1
  38. data/lib/action_pack/version.rb +1 -1
  39. metadata +7 -7
@@ -231,7 +231,12 @@ module ActionController
231
231
  # by the metal call stack.
232
232
  def process_action(*args)
233
233
  if _wrapper_enabled?
234
- wrapped_hash = _wrap_parameters request.request_parameters
234
+ if request.parameters[_wrapper_key].present?
235
+ wrapped_hash = _extract_parameters(request.parameters)
236
+ else
237
+ wrapped_hash = _wrap_parameters request.request_parameters
238
+ end
239
+
235
240
  wrapped_keys = request.request_parameters.keys
236
241
  wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
237
242
 
@@ -259,14 +264,16 @@ module ActionController
259
264
 
260
265
  # Returns the list of parameters which will be selected for wrapped.
261
266
  def _wrap_parameters(parameters)
262
- value = if include_only = _wrapper_options.include
267
+ { _wrapper_key => _extract_parameters(parameters) }
268
+ end
269
+
270
+ def _extract_parameters(parameters)
271
+ if include_only = _wrapper_options.include
263
272
  parameters.slice(*include_only)
264
273
  else
265
274
  exclude = _wrapper_options.exclude || []
266
275
  parameters.except(*(exclude + EXCLUDE_PARAMETERS))
267
276
  end
268
-
269
- { _wrapper_key => value }
270
277
  end
271
278
 
272
279
  # Checks if we should perform parameters wrapping.
@@ -5,8 +5,8 @@ module ActionController
5
5
  module RackDelegation
6
6
  extend ActiveSupport::Concern
7
7
 
8
- delegate :headers, :status=, :location=, :content_type=,
9
- :status, :location, :content_type, :to => "@_response"
8
+ delegate :headers, :status=, :location=, :content_type=, :no_content_type=,
9
+ :status, :location, :content_type, :no_content_type, :to => "@_response"
10
10
 
11
11
  def dispatch(action, request)
12
12
  set_response!(request)
@@ -43,7 +43,7 @@ module ActionController
43
43
  end
44
44
 
45
45
  # Hash of available renderers, mapping a renderer name to its proc.
46
- # Default keys are :json, :js, :xml.
46
+ # Default keys are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
47
47
  RENDERERS = Set.new
48
48
 
49
49
  # Adds a new renderer to call within controller actions.
@@ -2,6 +2,8 @@ module ActionController
2
2
  module Rendering
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
6
+
5
7
  # Before processing, set the request formats in current controller formats.
6
8
  def process_action(*) #:nodoc:
7
9
  self.formats = request.formats.map(&:ref).compact
@@ -27,15 +29,29 @@ module ActionController
27
29
  end
28
30
 
29
31
  def render_to_body(options = {})
30
- super || options[:text].presence || ' '
32
+ super || _render_in_priorities(options) || ' '
31
33
  end
32
34
 
33
35
  private
34
36
 
35
- def _process_format(format)
37
+ def _render_in_priorities(options)
38
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
39
+ return options[format] if options.key?(format)
40
+ end
41
+
42
+ nil
43
+ end
44
+
45
+ def _process_format(format, options = {})
36
46
  super
37
- # format is a Mime::NullType instance here then this condition can't be changed to `if format`
38
- self.content_type ||= format.to_s unless format.nil?
47
+
48
+ if options[:body]
49
+ self.headers.delete "Content-Type"
50
+ elsif options[:plain]
51
+ self.content_type = Mime::TEXT
52
+ else
53
+ self.content_type ||= format.to_s
54
+ end
39
55
  end
40
56
 
41
57
  # Normalize arguments by catching blocks and setting them on :update.
@@ -47,12 +63,14 @@ module ActionController
47
63
 
48
64
  # Normalize both text and status options.
49
65
  def _normalize_options(options) #:nodoc:
50
- if options.key?(:text) && options[:text].respond_to?(:to_text)
51
- options[:text] = options[:text].to_text
66
+ _normalize_text(options)
67
+
68
+ if options[:html]
69
+ options[:html] = ERB::Util.html_escape(options[:html])
52
70
  end
53
71
 
54
- if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?)
55
- options[:text] = " "
72
+ if options.delete(:nothing) || _any_render_format_is_nil?(options)
73
+ options[:body] = " "
56
74
  end
57
75
 
58
76
  if options[:status]
@@ -62,6 +80,18 @@ module ActionController
62
80
  super
63
81
  end
64
82
 
83
+ def _normalize_text(options)
84
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
85
+ if options.key?(format) && options[format].respond_to?(:to_text)
86
+ options[format] = options[format].to_text
87
+ end
88
+ end
89
+ end
90
+
91
+ def _any_render_format_is_nil?(options)
92
+ RENDER_FORMATS_IN_PRIORITY.any? { |format| options.key?(format) && options[format].nil? }
93
+ end
94
+
65
95
  # Process controller specific options, as status, content-type and location.
66
96
  def _process_options(options) #:nodoc:
67
97
  status, content_type, location = options.values_at(:status, :content_type, :location)
@@ -3,6 +3,7 @@ require 'active_support/core_ext/array/wrap'
3
3
  require 'active_support/rescuable'
4
4
  require 'action_dispatch/http/upload'
5
5
  require 'stringio'
6
+ require 'set'
6
7
 
7
8
  module ActionController
8
9
  # Raised when a required parameter is missing.
@@ -125,6 +126,13 @@ module ActionController
125
126
  @permitted = self.class.permit_all_parameters
126
127
  end
127
128
 
129
+ # Attribute that keeps track of converted arrays, if any, to avoid double
130
+ # looping in the common use case permit + mass-assignment. Defined in a
131
+ # method to instantiate it only if needed.
132
+ def converted_arrays
133
+ @converted_arrays ||= Set.new
134
+ end
135
+
128
136
  # Returns +true+ if the parameter is permitted, +false+ otherwise.
129
137
  #
130
138
  # params = ActionController::Parameters.new
@@ -149,8 +157,10 @@ module ActionController
149
157
  # Person.new(params) # => #<Person id: nil, name: "Francesco">
150
158
  def permit!
151
159
  each_pair do |key, value|
152
- convert_hashes_to_parameters(key, value)
153
- self[key].permit! if self[key].respond_to? :permit!
160
+ value = convert_hashes_to_parameters(key, value)
161
+ Array.wrap(value).each do |_|
162
+ _.permit! if _.respond_to? :permit!
163
+ end
154
164
  end
155
165
 
156
166
  @permitted = true
@@ -284,14 +294,7 @@ module ActionController
284
294
  # params.fetch(:none, 'Francesco') # => "Francesco"
285
295
  # params.fetch(:none) { 'Francesco' } # => "Francesco"
286
296
  def fetch(key, *args)
287
- value = super
288
- # Don't rely on +convert_hashes_to_parameters+
289
- # so as to not mutate via a +fetch+
290
- if value.is_a?(Hash)
291
- value = self.class.new(value)
292
- value.permit! if permitted?
293
- end
294
- value
297
+ convert_hashes_to_parameters(key, super, false)
295
298
  rescue KeyError
296
299
  raise ActionController::ParameterMissing.new(key)
297
300
  end
@@ -329,12 +332,21 @@ module ActionController
329
332
  end
330
333
 
331
334
  private
332
- def convert_hashes_to_parameters(key, value)
333
- if value.is_a?(Parameters) || !value.is_a?(Hash)
335
+ def convert_hashes_to_parameters(key, value, assign_if_converted=true)
336
+ converted = convert_value_to_parameters(value)
337
+ self[key] = converted if assign_if_converted && !converted.equal?(value)
338
+ converted
339
+ end
340
+
341
+ def convert_value_to_parameters(value)
342
+ if value.is_a?(Array) && !converted_arrays.member?(value)
343
+ converted = value.map { |_| convert_value_to_parameters(_) }
344
+ converted_arrays << converted
345
+ converted
346
+ elsif value.is_a?(Parameters) || !value.is_a?(Hash)
334
347
  value
335
348
  else
336
- # Convert to Parameters on first access
337
- self[key] = self.class.new(value)
349
+ self.class.new(value)
338
350
  end
339
351
  end
340
352
 
@@ -3,6 +3,7 @@ require "action_controller"
3
3
  require "action_dispatch/railtie"
4
4
  require "abstract_controller/railties/routes_helpers"
5
5
  require "action_controller/railties/helpers"
6
+ require "action_view/railtie"
6
7
 
7
8
  module ActionController
8
9
  class Railtie < Rails::Railtie #:nodoc:
@@ -213,6 +213,9 @@ module ActionController
213
213
  # Clear the combined params hash in case it was already referenced.
214
214
  @env.delete("action_dispatch.request.parameters")
215
215
 
216
+ # Clear the filter cache variables so they're not stale
217
+ @filtered_parameters = @filtered_env = @filtered_path = nil
218
+
216
219
  params = self.request_parameters.dup
217
220
  %w(controller action only_path).each do |k|
218
221
  params.delete(k)
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2004-2013 David Heinemeier Hansson
2
+ # Copyright (c) 2004-2014 David Heinemeier Hansson
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -74,18 +74,16 @@ module ActionDispatch
74
74
  autoload :MimeNegotiation
75
75
  autoload :Parameters
76
76
  autoload :ParameterFilter
77
- autoload :FilterParameters
78
- autoload :FilterRedirect
79
77
  autoload :Upload
80
78
  autoload :UploadedFile, 'action_dispatch/http/upload'
81
79
  autoload :URL
82
80
  end
83
81
 
84
82
  module Session
85
- autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
86
- autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
87
- autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
88
- autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
83
+ autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
84
+ autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
85
+ autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
86
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
89
87
  end
90
88
 
91
89
  mattr_accessor :test_app
@@ -5,7 +5,8 @@ module ActionDispatch
5
5
  FILTERED = '[FILTERED]'.freeze # :nodoc:
6
6
 
7
7
  def filtered_location
8
- if !location_filter.empty? && location_filter_match?
8
+ filters = location_filter
9
+ if !filters.empty? && location_filter_match?(filters)
9
10
  FILTERED
10
11
  else
11
12
  location
@@ -15,15 +16,15 @@ module ActionDispatch
15
16
  private
16
17
 
17
18
  def location_filter
18
- if request.present?
19
+ if request
19
20
  request.env['action_dispatch.redirect_filter'] || []
20
21
  else
21
22
  []
22
23
  end
23
24
  end
24
25
 
25
- def location_filter_match?
26
- location_filter.any? do |filter|
26
+ def location_filter_match?(filters)
27
+ filters.any? do |filter|
27
28
  if String === filter
28
29
  location.include?(filter)
29
30
  elsif Regexp === filter
@@ -50,7 +50,7 @@ module ActionDispatch
50
50
  # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first
51
51
  #
52
52
  def format(view_path = [])
53
- formats.first
53
+ formats.first || Mime::NullType.instance
54
54
  end
55
55
 
56
56
  def formats
@@ -68,10 +68,12 @@ module ActionDispatch
68
68
 
69
69
  # Sets the \variant for template.
70
70
  def variant=(variant)
71
- if variant.is_a? Symbol
71
+ if variant.is_a?(Symbol)
72
+ @variant = [variant]
73
+ elsif variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) }
72
74
  @variant = variant
73
75
  else
74
- raise ArgumentError, "request.variant must be set to a Symbol, not a #{variant.class}. " \
76
+ raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols, not a #{variant.class}. " \
75
77
  "For security reasons, never directly set the variant to a user-provided value, " \
76
78
  "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
77
79
  "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
@@ -28,7 +28,7 @@ module Mime
28
28
  class << self
29
29
  def [](type)
30
30
  return type if type.is_a?(Type)
31
- Type.lookup_by_extension(type) || NullType.instance
31
+ Type.lookup_by_extension(type)
32
32
  end
33
33
 
34
34
  def fetch(type)
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/module/attribute_accessors'
2
+ require 'action_dispatch/http/filter_redirect'
2
3
  require 'monitor'
3
4
 
4
5
  module ActionDispatch # :nodoc:
@@ -62,6 +63,8 @@ module ActionDispatch # :nodoc:
62
63
  # content you're giving them, so we need to send that along.
63
64
  attr_accessor :charset
64
65
 
66
+ attr_accessor :no_content_type # :nodoc:
67
+
65
68
  CONTENT_TYPE = "Content-Type".freeze
66
69
  SET_COOKIE = "Set-Cookie".freeze
67
70
  LOCATION = "Location".freeze
@@ -302,8 +305,17 @@ module ActionDispatch # :nodoc:
302
305
  !@sending_file && @charset != false
303
306
  end
304
307
 
308
+ def remove_content_type!
309
+ headers.delete CONTENT_TYPE
310
+ end
311
+
305
312
  def rack_response(status, header)
306
- assign_default_content_type_and_charset!(header)
313
+ if no_content_type
314
+ remove_content_type!
315
+ else
316
+ assign_default_content_type_and_charset!(header)
317
+ end
318
+
307
319
  handle_conditional_get!
308
320
 
309
321
  header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
@@ -312,7 +324,7 @@ module ActionDispatch # :nodoc:
312
324
  header.delete CONTENT_TYPE
313
325
  [status, header, []]
314
326
  else
315
- [status, header, self]
327
+ [status, header, Rack::BodyProxy.new(self){}]
316
328
  end
317
329
  end
318
330
  end
@@ -33,8 +33,8 @@ module ActionDispatch
33
33
  return [route.format(parameterized_parts), params]
34
34
  end
35
35
 
36
- message = "No route matches #{constraints.inspect}"
37
- message << " missing required keys: #{missing_keys.inspect}" if name
36
+ message = "No route matches #{Hash[constraints.sort].inspect}"
37
+ message << " missing required keys: #{missing_keys.sort.inspect}" if name
38
38
 
39
39
  raise ActionController::UrlGenerationError, message
40
40
  end
@@ -54,7 +54,7 @@ module ActionDispatch
54
54
  end
55
55
 
56
56
  def call(env)
57
- env['PATH_INFO'] = normalize_path(env['PATH_INFO'])
57
+ env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO'])
58
58
 
59
59
  find_routes(env).each do |match, parameters, route|
60
60
  script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
@@ -103,12 +103,6 @@ module ActionDispatch
103
103
 
104
104
  private
105
105
 
106
- def normalize_path(path)
107
- path = "/#{path}"
108
- path.squeeze!('/')
109
- path
110
- end
111
-
112
106
  def partitioned_routes
113
107
  routes.partitioned_routes
114
108
  end
@@ -77,12 +77,32 @@ module ActionDispatch
77
77
  end
78
78
  end
79
79
 
80
- class OptimizedPath < String # :nodoc:
80
+ class OptimizedPath < Visitor # :nodoc:
81
+ def accept(node)
82
+ Array(visit(node))
83
+ end
84
+
81
85
  private
82
86
 
83
- def visit_GROUP(node)
84
- ""
85
- end
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
86
106
  end
87
107
 
88
108
  # Used for formatting urls (url_for)
@@ -23,15 +23,15 @@ module ActionDispatch
23
23
  # # This cookie will be deleted when the user's browser is closed.
24
24
  # cookies[:user_name] = "david"
25
25
  #
26
- # # Assign an array of values to a cookie.
27
- # cookies[:lat_lon] = [47.68, -122.37]
26
+ # # Cookie values are String based. Other data types need to be serialized.
27
+ # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
28
28
  #
29
29
  # # Sets a cookie that expires in 1 hour.
30
30
  # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
31
31
  #
32
32
  # # Sets a signed cookie, which prevents users from tampering with its value.
33
- # # The cookie is signed by your app's <tt>secrets.secret_key_base</tt> value.
34
- # # It can be read using the signed method <tt>cookies.signed[:name]</tt>
33
+ # # The cookie is signed by your app's `secrets.secret_key_base` value.
34
+ # # It can be read using the signed method `cookies.signed[:name]`
35
35
  # cookies.signed[:user_id] = current_user.id
36
36
  #
37
37
  # # Sets a "permanent" cookie (which expires in 20 years from now).
@@ -42,10 +42,10 @@ module ActionDispatch
42
42
  #
43
43
  # Examples of reading:
44
44
  #
45
- # cookies[:user_name] # => "david"
46
- # cookies.size # => 2
47
- # cookies[:lat_lon] # => [47.68, -122.37]
48
- # cookies.signed[:login] # => "XJ-122"
45
+ # cookies[:user_name] # => "david"
46
+ # cookies.size # => 2
47
+ # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37]
48
+ # cookies.signed[:login] # => "XJ-122"
49
49
  #
50
50
  # Example for deleting:
51
51
  #
@@ -63,7 +63,7 @@ module ActionDispatch
63
63
  #
64
64
  # The option symbols for setting cookies are:
65
65
  #
66
- # * <tt>:value</tt> - The cookie's value or list of values (as an array).
66
+ # * <tt>:value</tt> - The cookie's value.
67
67
  # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
68
68
  # of the application.
69
69
  # * <tt>:domain</tt> - The domain for which this cookie applies so you can
@@ -89,6 +89,7 @@ module ActionDispatch
89
89
  ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
90
90
  SECRET_TOKEN = "action_dispatch.secret_token".freeze
91
91
  SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
92
+ COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
92
93
 
93
94
  # Cookies can typically store 4096 bytes.
94
95
  MAX_COOKIE_SIZE = 4096
@@ -180,7 +181,7 @@ module ActionDispatch
180
181
 
181
182
  def verify_and_upgrade_legacy_signed_message(name, signed_message)
182
183
  @legacy_verifier.verify(signed_message).tap do |value|
183
- self[name] = value
184
+ self[name] = { value: value }
184
185
  end
185
186
  rescue ActiveSupport::MessageVerifier::InvalidSignature
186
187
  nil
@@ -210,7 +211,8 @@ module ActionDispatch
210
211
  encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
211
212
  secret_token: env[SECRET_TOKEN],
212
213
  secret_key_base: env[SECRET_KEY_BASE],
213
- upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
214
+ upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
215
+ serializer: env[COOKIES_SERIALIZER]
214
216
  }
215
217
  end
216
218
 
@@ -372,28 +374,89 @@ module ActionDispatch
372
374
  end
373
375
  end
374
376
 
377
+ class JsonSerializer
378
+ def self.load(value)
379
+ JSON.parse(value, quirks_mode: true)
380
+ end
381
+
382
+ def self.dump(value)
383
+ JSON.generate(value, quirks_mode: true)
384
+ end
385
+ end
386
+
387
+ # Passing the NullSerializer downstream to the Message{Encryptor,Verifier}
388
+ # allows us to handle the (de)serialization step within the cookie jar,
389
+ # which gives us the opportunity to detect and migrate legacy cookies.
390
+ class NullSerializer
391
+ def self.load(value)
392
+ value
393
+ end
394
+
395
+ def self.dump(value)
396
+ value
397
+ end
398
+ end
399
+
400
+ module SerializedCookieJars
401
+ MARSHAL_SIGNATURE = "\x04\x08".freeze
402
+
403
+ protected
404
+ def needs_migration?(value)
405
+ @options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
406
+ end
407
+
408
+ def serialize(name, value)
409
+ serializer.dump(value)
410
+ end
411
+
412
+ def deserialize(name, value)
413
+ if value
414
+ if needs_migration?(value)
415
+ Marshal.load(value).tap do |v|
416
+ self[name] = { value: v }
417
+ end
418
+ else
419
+ serializer.load(value)
420
+ end
421
+ end
422
+ end
423
+
424
+ def serializer
425
+ serializer = @options[:serializer] || :marshal
426
+ case serializer
427
+ when :marshal
428
+ Marshal
429
+ when :json, :hybrid
430
+ JsonSerializer
431
+ else
432
+ serializer
433
+ end
434
+ end
435
+ end
436
+
375
437
  class SignedCookieJar #:nodoc:
376
438
  include ChainedCookieJars
439
+ include SerializedCookieJars
377
440
 
378
441
  def initialize(parent_jar, key_generator, options = {})
379
442
  @parent_jar = parent_jar
380
443
  @options = options
381
444
  secret = key_generator.generate_key(@options[:signed_cookie_salt])
382
- @verifier = ActiveSupport::MessageVerifier.new(secret)
445
+ @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: NullSerializer)
383
446
  end
384
447
 
385
448
  def [](name)
386
449
  if signed_message = @parent_jar[name]
387
- verify(signed_message)
450
+ deserialize name, verify(signed_message)
388
451
  end
389
452
  end
390
453
 
391
454
  def []=(name, options)
392
455
  if options.is_a?(Hash)
393
456
  options.symbolize_keys!
394
- options[:value] = @verifier.generate(options[:value])
457
+ options[:value] = @verifier.generate(serialize(name, options[:value]))
395
458
  else
396
- options = { :value => @verifier.generate(options) }
459
+ options = { :value => @verifier.generate(serialize(name, options)) }
397
460
  end
398
461
 
399
462
  raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@@ -417,13 +480,14 @@ module ActionDispatch
417
480
 
418
481
  def [](name)
419
482
  if signed_message = @parent_jar[name]
420
- verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
483
+ deserialize(name, verify(signed_message)) || verify_and_upgrade_legacy_signed_message(name, signed_message)
421
484
  end
422
485
  end
423
486
  end
424
487
 
425
488
  class EncryptedCookieJar #:nodoc:
426
489
  include ChainedCookieJars
490
+ include SerializedCookieJars
427
491
 
428
492
  def initialize(parent_jar, key_generator, options = {})
429
493
  if ActiveSupport::LegacyKeyGenerator === key_generator
@@ -435,12 +499,12 @@ module ActionDispatch
435
499
  @options = options
436
500
  secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
437
501
  sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
438
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
502
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: NullSerializer)
439
503
  end
440
504
 
441
505
  def [](name)
442
506
  if encrypted_message = @parent_jar[name]
443
- decrypt_and_verify(encrypted_message)
507
+ deserialize name, decrypt_and_verify(encrypted_message)
444
508
  end
445
509
  end
446
510
 
@@ -450,7 +514,8 @@ module ActionDispatch
450
514
  else
451
515
  options = { :value => options }
452
516
  end
453
- options[:value] = @encryptor.encrypt_and_sign(options[:value])
517
+
518
+ options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value]))
454
519
 
455
520
  raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
456
521
  @parent_jar[name] = options
@@ -473,7 +538,7 @@ module ActionDispatch
473
538
 
474
539
  def [](name)
475
540
  if encrypted_or_signed_message = @parent_jar[name]
476
- decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
541
+ deserialize(name, decrypt_and_verify(encrypted_or_signed_message)) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
477
542
  end
478
543
  end
479
544
  end