actionpack 3.0.0.beta3 → 3.0.0.beta4

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 (83) hide show
  1. data/CHANGELOG +19 -0
  2. data/lib/abstract_controller.rb +1 -1
  3. data/lib/abstract_controller/asset_paths.rb +9 -0
  4. data/lib/abstract_controller/base.rb +5 -13
  5. data/lib/abstract_controller/callbacks.rb +1 -1
  6. data/lib/abstract_controller/helpers.rb +0 -1
  7. data/lib/abstract_controller/layouts.rb +3 -3
  8. data/lib/abstract_controller/logger.rb +1 -1
  9. data/lib/abstract_controller/rendering.rb +1 -0
  10. data/lib/action_controller/base.rb +5 -1
  11. data/lib/action_controller/caching.rb +2 -3
  12. data/lib/action_controller/caching/actions.rb +1 -1
  13. data/lib/action_controller/caching/fragments.rb +1 -1
  14. data/lib/action_controller/caching/pages.rb +8 -8
  15. data/lib/action_controller/caching/sweeping.rb +1 -0
  16. data/lib/action_controller/deprecated/base.rb +10 -36
  17. data/lib/action_controller/metal.rb +45 -3
  18. data/lib/action_controller/metal/compatibility.rb +2 -2
  19. data/lib/action_controller/metal/helpers.rb +3 -3
  20. data/lib/action_controller/metal/http_authentication.rb +158 -0
  21. data/lib/action_controller/metal/instrumentation.rb +5 -5
  22. data/lib/action_controller/metal/rack_delegation.rb +4 -4
  23. data/lib/action_controller/metal/renderers.rb +3 -3
  24. data/lib/action_controller/metal/request_forgery_protection.rb +45 -74
  25. data/lib/action_controller/metal/responder.rb +1 -1
  26. data/lib/action_controller/metal/url_for.rb +8 -0
  27. data/lib/action_controller/railtie.rb +26 -39
  28. data/lib/action_controller/test_case.rb +147 -135
  29. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -0
  30. data/lib/action_dispatch.rb +0 -1
  31. data/lib/action_dispatch/http/parameters.rb +2 -1
  32. data/lib/action_dispatch/http/request.rb +19 -7
  33. data/lib/action_dispatch/http/response.rb +3 -33
  34. data/lib/action_dispatch/middleware/cookies.rb +44 -10
  35. data/lib/action_dispatch/middleware/flash.rb +11 -1
  36. data/lib/action_dispatch/middleware/params_parser.rb +3 -1
  37. data/lib/action_dispatch/middleware/session/abstract_store.rb +47 -83
  38. data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -165
  39. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +2 -2
  40. data/lib/action_dispatch/middleware/show_exceptions.rb +18 -12
  41. data/lib/action_dispatch/middleware/stack.rb +17 -67
  42. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +1 -1
  43. data/lib/action_dispatch/railtie.rb +0 -2
  44. data/lib/action_dispatch/routing/deprecated_mapper.rb +1 -0
  45. data/lib/action_dispatch/routing/mapper.rb +89 -23
  46. data/lib/action_dispatch/routing/route_set.rb +22 -16
  47. data/lib/action_dispatch/routing/url_for.rb +1 -1
  48. data/lib/action_dispatch/testing/assertions/routing.rb +1 -0
  49. data/lib/action_dispatch/testing/assertions/selector.rb +11 -7
  50. data/lib/action_dispatch/testing/test_process.rb +3 -2
  51. data/lib/action_pack/version.rb +1 -1
  52. data/lib/action_view.rb +5 -1
  53. data/lib/action_view/base.rb +10 -4
  54. data/lib/action_view/helpers/active_model_helper.rb +1 -8
  55. data/lib/action_view/helpers/asset_tag_helper.rb +7 -4
  56. data/lib/action_view/helpers/cache_helper.rb +14 -14
  57. data/lib/action_view/helpers/capture_helper.rb +25 -6
  58. data/lib/action_view/helpers/date_helper.rb +33 -44
  59. data/lib/action_view/helpers/form_helper.rb +47 -27
  60. data/lib/action_view/helpers/form_options_helper.rb +26 -3
  61. data/lib/action_view/helpers/form_tag_helper.rb +8 -4
  62. data/lib/action_view/helpers/number_helper.rb +5 -2
  63. data/lib/action_view/helpers/prototype_helper.rb +1 -1
  64. data/lib/action_view/helpers/tag_helper.rb +1 -1
  65. data/lib/action_view/helpers/text_helper.rb +55 -46
  66. data/lib/action_view/helpers/translation_helper.rb +19 -8
  67. data/lib/action_view/helpers/url_helper.rb +2 -4
  68. data/lib/action_view/locale/en.yml +14 -14
  69. data/lib/action_view/lookup_context.rb +52 -22
  70. data/lib/action_view/paths.rb +1 -0
  71. data/lib/action_view/render/layouts.rb +3 -12
  72. data/lib/action_view/render/partials.rb +21 -10
  73. data/lib/action_view/render/rendering.rb +1 -1
  74. data/lib/action_view/template.rb +172 -26
  75. data/lib/action_view/template/error.rb +25 -27
  76. data/lib/action_view/template/handlers.rb +1 -1
  77. data/lib/action_view/template/handlers/erb.rb +92 -45
  78. data/lib/action_view/template/resolver.rb +4 -1
  79. data/lib/action_view/test_case.rb +105 -72
  80. data/lib/action_view/testing/resolvers.rb +43 -0
  81. metadata +62 -20
  82. data/lib/abstract_controller/assigns.rb +0 -21
  83. data/lib/action_dispatch/middleware/cascade.rb +0 -29
@@ -1,6 +1,7 @@
1
1
  require 'digest/md5'
2
2
  require 'active_support/core_ext/module/delegation'
3
3
  require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/class/attribute_accessors'
4
5
 
5
6
  module ActionDispatch # :nodoc:
6
7
  # Represents an HTTP response generated by a controller action. One can use
@@ -139,7 +140,7 @@ module ActionDispatch # :nodoc:
139
140
  def to_a
140
141
  assign_default_content_type_and_charset!
141
142
  handle_conditional_get!
142
- self["Set-Cookie"] = @cookie.join("\n") unless @cookie.blank?
143
+ self["Set-Cookie"] = self["Set-Cookie"].join("\n") if self["Set-Cookie"].respond_to?(:join)
143
144
  self["ETag"] = @_etag if @_etag
144
145
  super
145
146
  end
@@ -169,7 +170,7 @@ module ActionDispatch # :nodoc:
169
170
  # assert_equal 'AuthorOfNewPage', r.cookies['author']
170
171
  def cookies
171
172
  cookies = {}
172
- if header = @cookie
173
+ if header = self["Set-Cookie"]
173
174
  header = header.split("\n") if header.respond_to?(:to_str)
174
175
  header.each do |cookie|
175
176
  if pair = cookie.split(';').first
@@ -181,37 +182,6 @@ module ActionDispatch # :nodoc:
181
182
  cookies
182
183
  end
183
184
 
184
- def set_cookie(key, value)
185
- case value
186
- when Hash
187
- domain = "; domain=" + value[:domain] if value[:domain]
188
- path = "; path=" + value[:path] if value[:path]
189
- # According to RFC 2109, we need dashes here.
190
- # N.B.: cgi.rb uses spaces...
191
- expires = "; expires=" + value[:expires].clone.gmtime.
192
- strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
193
- secure = "; secure" if value[:secure]
194
- httponly = "; HttpOnly" if value[:httponly]
195
- value = value[:value]
196
- end
197
- value = [value] unless Array === value
198
- cookie = Rack::Utils.escape(key) + "=" +
199
- value.map { |v| Rack::Utils.escape v }.join("&") +
200
- "#{domain}#{path}#{expires}#{secure}#{httponly}"
201
-
202
- @cookie << cookie
203
- end
204
-
205
- def delete_cookie(key, value={})
206
- @cookie.reject! { |cookie|
207
- cookie =~ /\A#{Rack::Utils.escape(key)}=/
208
- }
209
-
210
- set_cookie(key,
211
- {:value => '', :path => nil, :domain => nil,
212
- :expires => Time.at(0) }.merge(value))
213
- end
214
-
215
185
  private
216
186
  def assign_default_content_type_and_charset!
217
187
  return if headers[CONTENT_TYPE].present?
@@ -52,9 +52,15 @@ module ActionDispatch
52
52
  # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
53
53
  # only HTTP. Defaults to +false+.
54
54
  class Cookies
55
+ HTTP_HEADER = "Set-Cookie".freeze
56
+ TOKEN_KEY = "action_dispatch.secret_token".freeze
57
+
58
+ # Raised when storing more than 4K of session data.
59
+ class CookieOverflow < StandardError; end
60
+
55
61
  class CookieJar < Hash #:nodoc:
56
62
  def self.build(request)
57
- secret = request.env["action_dispatch.secret_token"]
63
+ secret = request.env[TOKEN_KEY]
58
64
  new(secret).tap do |hash|
59
65
  hash.update(request.cookies)
60
66
  end
@@ -134,9 +140,9 @@ module ActionDispatch
134
140
  @signed ||= SignedCookieJar.new(self, @secret)
135
141
  end
136
142
 
137
- def write(response)
138
- @set_cookies.each { |k, v| response.set_cookie(k, v) }
139
- @delete_cookies.each { |k, v| response.delete_cookie(k, v) }
143
+ def write(headers)
144
+ @set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) }
145
+ @delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
140
146
  end
141
147
  end
142
148
 
@@ -166,8 +172,11 @@ module ActionDispatch
166
172
  end
167
173
 
168
174
  class SignedCookieJar < CookieJar #:nodoc:
175
+ MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
176
+ SECRET_MIN_LENGTH = 30 # Characters
177
+
169
178
  def initialize(parent_jar, secret)
170
- raise "You must set config.secret_token in your app's config" if secret.blank?
179
+ ensure_secret_secure(secret)
171
180
  @parent_jar = parent_jar
172
181
  @verifier = ActiveSupport::MessageVerifier.new(secret)
173
182
  end
@@ -176,6 +185,8 @@ module ActionDispatch
176
185
  if signed_message = @parent_jar[name]
177
186
  @verifier.verify(signed_message)
178
187
  end
188
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
189
+ nil
179
190
  end
180
191
 
181
192
  def []=(key, options)
@@ -186,12 +197,34 @@ module ActionDispatch
186
197
  options = { :value => @verifier.generate(options) }
187
198
  end
188
199
 
200
+ raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
189
201
  @parent_jar[key] = options
190
202
  end
191
203
 
192
204
  def method_missing(method, *arguments, &block)
193
205
  @parent_jar.send(method, *arguments, &block)
194
206
  end
207
+
208
+ protected
209
+
210
+ # To prevent users from using something insecure like "Password" we make sure that the
211
+ # secret they've provided is at least 30 characters in length.
212
+ def ensure_secret_secure(secret)
213
+ if secret.blank?
214
+ raise ArgumentError, "A secret is required to generate an " +
215
+ "integrity hash for cookie session data. Use " +
216
+ "config.secret_token = \"some secret phrase of at " +
217
+ "least #{SECRET_MIN_LENGTH} characters\"" +
218
+ "in config/application.rb"
219
+ end
220
+
221
+ if secret.length < SECRET_MIN_LENGTH
222
+ raise ArgumentError, "Secret should be something secure, " +
223
+ "like \"#{ActiveSupport::SecureRandom.hex(16)}\". The value you " +
224
+ "provided, \"#{secret}\", is shorter than the minimum length " +
225
+ "of #{SECRET_MIN_LENGTH} characters"
226
+ end
227
+ end
195
228
  end
196
229
 
197
230
  def initialize(app)
@@ -202,12 +235,13 @@ module ActionDispatch
202
235
  status, headers, body = @app.call(env)
203
236
 
204
237
  if cookie_jar = env['action_dispatch.cookies']
205
- response = Rack::Response.new(body, status, headers)
206
- cookie_jar.write(response)
207
- response.to_a
208
- else
209
- [status, headers, body]
238
+ cookie_jar.write(headers)
239
+ if headers[HTTP_HEADER].respond_to?(:join)
240
+ headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
241
+ end
210
242
  end
243
+
244
+ [status, headers, body]
211
245
  end
212
246
  end
213
247
  end
@@ -49,6 +49,16 @@ module ActionDispatch
49
49
  def [](k)
50
50
  @flash[k]
51
51
  end
52
+
53
+ # Convenience accessor for flash.now[:alert]=
54
+ def alert=(message)
55
+ self[:alert] = message
56
+ end
57
+
58
+ # Convenience accessor for flash.now[:notice]=
59
+ def notice=(message)
60
+ self[:notice] = message
61
+ end
52
62
  end
53
63
 
54
64
  class FlashHash < Hash
@@ -166,7 +176,7 @@ module ActionDispatch
166
176
 
167
177
  @app.call(env)
168
178
  ensure
169
- if (session = env['rack.session']) && (flash = session['flash']) && flash.empty?
179
+ if (session = env['rack.session']) && session.key?('flash') && session['flash'].empty?
170
180
  session.delete('flash')
171
181
  end
172
182
  end
@@ -1,4 +1,6 @@
1
+ require 'active_support/core_ext/hash/conversions'
1
2
  require 'action_dispatch/http/request'
3
+ require 'active_support/core_ext/hash/indifferent_access'
2
4
 
3
5
  module ActionDispatch
4
6
  class ParamsParser
@@ -36,7 +38,7 @@ module ActionDispatch
36
38
  when Proc
37
39
  strategy.call(request.raw_post)
38
40
  when :xml_simple, :xml_node
39
- data = Hash.from_xml(request.body) || {}
41
+ data = Hash.from_xml(request.body.read) || {}
40
42
  request.body.rewind if request.body.respond_to?(:rewind)
41
43
  data.with_indifferent_access
42
44
  when :yaml
@@ -1,5 +1,6 @@
1
1
  require 'rack/utils'
2
2
  require 'rack/request'
3
+ require 'action_dispatch/middleware/cookies'
3
4
  require 'active_support/core_ext/object/blank'
4
5
 
5
6
  module ActionDispatch
@@ -11,9 +12,6 @@ module ActionDispatch
11
12
  ENV_SESSION_KEY = 'rack.session'.freeze
12
13
  ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
13
14
 
14
- HTTP_COOKIE = 'HTTP_COOKIE'.freeze
15
- SET_COOKIE = 'Set-Cookie'.freeze
16
-
17
15
  class SessionHash < Hash
18
16
  def initialize(by, env)
19
17
  super()
@@ -22,13 +20,6 @@ module ActionDispatch
22
20
  @loaded = false
23
21
  end
24
22
 
25
- def session_id
26
- ActiveSupport::Deprecation.warn(
27
- "ActionDispatch::Session::AbstractStore::SessionHash#session_id " +
28
- "has been deprecated. Please use request.session_options[:id] instead.", caller)
29
- @env[ENV_SESSION_OPTIONS_KEY][:id]
30
- end
31
-
32
23
  def [](key)
33
24
  load! unless @loaded
34
25
  super(key.to_s)
@@ -45,35 +36,14 @@ module ActionDispatch
45
36
  h
46
37
  end
47
38
 
48
- def update(hash = nil)
49
- if hash.nil?
50
- ActiveSupport::Deprecation.warn('use replace instead', caller)
51
- replace({})
52
- else
53
- load! unless @loaded
54
- super(hash.stringify_keys)
55
- end
56
- end
57
-
58
- def delete(key = nil)
59
- if key.nil?
60
- ActiveSupport::Deprecation.warn('use clear instead', caller)
61
- clear
62
- else
63
- load! unless @loaded
64
- super(key.to_s)
65
- end
66
- end
67
-
68
- def data
69
- ActiveSupport::Deprecation.warn(
70
- "ActionDispatch::Session::AbstractStore::SessionHash#data " +
71
- "has been deprecated. Please use #to_hash instead.", caller)
72
- to_hash
39
+ def update(hash)
40
+ load! unless @loaded
41
+ super(hash.stringify_keys)
73
42
  end
74
43
 
75
- def close
76
- ActiveSupport::Deprecation.warn('sessions should no longer be closed', caller)
44
+ def delete(key)
45
+ load! unless @loaded
46
+ super(key.to_s)
77
47
  end
78
48
 
79
49
  def inspect
@@ -81,11 +51,11 @@ module ActionDispatch
81
51
  super
82
52
  end
83
53
 
84
- private
85
- def loaded?
86
- @loaded
87
- end
54
+ def loaded?
55
+ @loaded
56
+ end
88
57
 
58
+ private
89
59
  def load!
90
60
  stale_session_check! do
91
61
  id, session = @by.send(:load_session, @env)
@@ -124,30 +94,15 @@ module ActionDispatch
124
94
  }
125
95
 
126
96
  def initialize(app, options = {})
127
- # Process legacy CGI options
128
- options = options.symbolize_keys
129
- if options.has_key?(:session_path)
130
- options[:path] = options.delete(:session_path)
131
- end
132
- if options.has_key?(:session_key)
133
- options[:key] = options.delete(:session_key)
134
- end
135
- if options.has_key?(:session_http_only)
136
- options[:httponly] = options.delete(:session_http_only)
137
- end
138
-
139
97
  @app = app
140
98
  @default_options = DEFAULT_OPTIONS.merge(options)
141
- @key = @default_options[:key]
142
- @cookie_only = @default_options[:cookie_only]
99
+ @key = @default_options.delete(:key).freeze
100
+ @cookie_only = @default_options.delete(:cookie_only)
101
+ ensure_session_key!
143
102
  end
144
103
 
145
104
  def call(env)
146
- session = SessionHash.new(self, env)
147
-
148
- env[ENV_SESSION_KEY] = session
149
- env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
150
-
105
+ prepare!(env)
151
106
  response = @app.call(env)
152
107
 
153
108
  session_data = env[ENV_SESSION_KEY]
@@ -157,53 +112,62 @@ module ActionDispatch
157
112
  session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
158
113
 
159
114
  sid = options[:id] || generate_sid
115
+ session_data = session_data.to_hash
160
116
 
161
- unless set_session(env, sid, session_data.to_hash)
162
- return response
163
- end
117
+ value = set_session(env, sid, session_data)
118
+ return response unless value
164
119
 
165
- cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid)
166
- cookie << "; domain=#{options[:domain]}" if options[:domain]
167
- cookie << "; path=#{options[:path]}" if options[:path]
168
- if options[:expire_after]
169
- expiry = Time.now + options[:expire_after]
170
- cookie << "; expires=#{expiry.httpdate}"
171
- end
172
- cookie << "; Secure" if options[:secure]
173
- cookie << "; HttpOnly" if options[:httponly]
174
-
175
- headers = response[1]
176
- unless headers[SET_COOKIE].blank?
177
- headers[SET_COOKIE] << "\n#{cookie}"
178
- else
179
- headers[SET_COOKIE] = cookie
120
+ cookie = { :value => value }
121
+ unless options[:expire_after].nil?
122
+ cookie[:expires] = Time.now + options.delete(:expire_after)
180
123
  end
124
+
125
+ request = ActionDispatch::Request.new(env)
126
+ set_cookie(request, cookie.merge!(options))
181
127
  end
182
128
 
183
129
  response
184
130
  end
185
131
 
186
132
  private
133
+
134
+ def prepare!(env)
135
+ env[ENV_SESSION_KEY] = SessionHash.new(self, env)
136
+ env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
137
+ end
138
+
187
139
  def generate_sid
188
140
  ActiveSupport::SecureRandom.hex(16)
189
141
  end
190
142
 
143
+ def set_cookie(request, options)
144
+ request.cookie_jar[@key] = options
145
+ end
146
+
191
147
  def load_session(env)
192
148
  request = Rack::Request.new(env)
193
- sid = request.cookies[@key]
194
- unless @cookie_only
195
- sid ||= request.params[@key]
196
- end
149
+ sid = request.cookies[@key]
150
+ sid ||= request.params[@key] unless @cookie_only
197
151
  sid, session = get_session(env, sid)
198
152
  [sid, session]
199
153
  end
200
154
 
155
+ def ensure_session_key!
156
+ if @key.blank?
157
+ raise ArgumentError, 'A key is required to write a ' +
158
+ 'cookie containing the session data. Use ' +
159
+ 'config.session_store SESSION_STORE, { :key => ' +
160
+ '"_myapp_session" } in config/application.rb'
161
+ end
162
+ end
163
+
201
164
  def get_session(env, sid)
202
165
  raise '#get_session needs to be implemented.'
203
166
  end
204
167
 
205
168
  def set_session(env, sid, session_data)
206
- raise '#set_session needs to be implemented.'
169
+ raise '#set_session needs to be implemented and should return ' <<
170
+ 'the value to be stored in the cookie (usually the sid)'
207
171
  end
208
172
  end
209
173
  end
@@ -38,23 +38,11 @@ module ActionDispatch
38
38
  # "rake secret" and set the key in config/environment.rb.
39
39
  #
40
40
  # Note that changing digest or secret invalidates all existing sessions!
41
- class CookieStore
42
- # Cookies can typically store 4096 bytes.
43
- MAX = 4096
44
- SECRET_MIN_LENGTH = 30 # characters
45
-
46
- DEFAULT_OPTIONS = {
47
- :key => '_session_id',
48
- :domain => nil,
49
- :path => "/",
50
- :expire_after => nil,
51
- :httponly => true
52
- }.freeze
53
-
41
+ class CookieStore < AbstractStore
54
42
  class OptionsHash < Hash
55
43
  def initialize(by, env, default_options)
56
- @session_data = env[CookieStore::ENV_SESSION_KEY]
57
- default_options.each { |key, value| self[key] = value }
44
+ @session_data = env[AbstractStore::ENV_SESSION_KEY]
45
+ merge!(default_options)
58
46
  end
59
47
 
60
48
  def [](key)
@@ -62,172 +50,38 @@ module ActionDispatch
62
50
  end
63
51
  end
64
52
 
65
- ENV_SESSION_KEY = "rack.session".freeze
66
- ENV_SESSION_OPTIONS_KEY = "rack.session.options".freeze
67
- HTTP_SET_COOKIE = "Set-Cookie".freeze
68
-
69
- # Raised when storing more than 4K of session data.
70
- class CookieOverflow < StandardError; end
71
-
72
53
  def initialize(app, options = {})
73
- # Process legacy CGI options
74
- options = options.symbolize_keys
75
- if options.has_key?(:session_path)
76
- options[:path] = options.delete(:session_path)
77
- end
78
- if options.has_key?(:session_key)
79
- options[:key] = options.delete(:session_key)
80
- end
81
- if options.has_key?(:session_http_only)
82
- options[:httponly] = options.delete(:session_http_only)
83
- end
84
-
85
- @app = app
86
-
87
- # The session_key option is required.
88
- ensure_session_key(options[:key])
89
- @key = options.delete(:key).freeze
90
-
91
- # The secret option is required.
92
- ensure_secret_secure(options[:secret])
93
- @secret = options.delete(:secret).freeze
94
-
95
- @digest = options.delete(:digest) || 'SHA1'
96
- @verifier = verifier_for(@secret, @digest)
97
-
98
- @default_options = DEFAULT_OPTIONS.merge(options).freeze
99
-
54
+ super(app, options.merge!(:cookie_only => true))
100
55
  freeze
101
56
  end
102
57
 
103
- def call(env)
104
- env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
105
- env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
106
-
107
- status, headers, body = @app.call(env)
108
-
109
- session_data = env[ENV_SESSION_KEY]
110
- options = env[ENV_SESSION_OPTIONS_KEY]
111
-
112
- if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
113
- session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
114
- session_data = marshal(session_data.to_hash)
115
-
116
- raise CookieOverflow if session_data.size > MAX
117
-
118
- cookie = Hash.new
119
- cookie[:value] = session_data
120
- unless options[:expire_after].nil?
121
- cookie[:expires] = Time.now + options[:expire_after]
122
- end
123
-
124
- cookie = build_cookie(@key, cookie.merge(options))
125
- unless headers[HTTP_SET_COOKIE].blank?
126
- headers[HTTP_SET_COOKIE] << "\n#{cookie}"
127
- else
128
- headers[HTTP_SET_COOKIE] = cookie
129
- end
130
- end
131
-
132
- [status, headers, body]
133
- end
134
-
135
58
  private
136
- # Should be in Rack::Utils soon
137
- def build_cookie(key, value)
138
- case value
139
- when Hash
140
- domain = "; domain=" + value[:domain] if value[:domain]
141
- path = "; path=" + value[:path] if value[:path]
142
- # According to RFC 2109, we need dashes here.
143
- # N.B.: cgi.rb uses spaces...
144
- expires = "; expires=" + value[:expires].clone.gmtime.
145
- strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
146
- secure = "; secure" if value[:secure]
147
- httponly = "; HttpOnly" if value[:httponly]
148
- value = value[:value]
149
- end
150
- value = [value] unless Array === value
151
- cookie = Rack::Utils.escape(key) + "=" +
152
- value.map { |v| Rack::Utils.escape(v) }.join("&") +
153
- "#{domain}#{path}#{expires}#{secure}#{httponly}"
59
+
60
+ def prepare!(env)
61
+ env[ENV_SESSION_KEY] = SessionHash.new(self, env)
62
+ env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
154
63
  end
155
64
 
156
65
  def load_session(env)
157
- request = Rack::Request.new(env)
158
- session_data = request.cookies[@key]
159
- data = unmarshal(session_data) || persistent_session_id!({})
66
+ request = ActionDispatch::Request.new(env)
67
+ data = request.cookie_jar.signed[@key]
68
+ data = persistent_session_id!(data)
160
69
  data.stringify_keys!
161
70
  [data["session_id"], data]
162
71
  end
163
72
 
164
- # Marshal a session hash into safe cookie data. Include an integrity hash.
165
- def marshal(session)
166
- @verifier.generate(persistent_session_id!(session))
167
- end
168
-
169
- # Unmarshal cookie data to a hash and verify its integrity.
170
- def unmarshal(cookie)
171
- persistent_session_id!(@verifier.verify(cookie)) if cookie
172
- rescue ActiveSupport::MessageVerifier::InvalidSignature
173
- nil
174
- end
175
-
176
- def ensure_session_key(key)
177
- if key.blank?
178
- raise ArgumentError, 'A key is required to write a ' +
179
- 'cookie containing the session data. Use ' +
180
- 'config.action_controller.session_store :cookie_store, { :key => ' +
181
- '"_myapp_session" } in config/application.rb'
182
- end
183
- end
184
-
185
- # To prevent users from using something insecure like "Password" we make sure that the
186
- # secret they've provided is at least 30 characters in length.
187
- def ensure_secret_secure(secret)
188
- # There's no way we can do this check if they've provided a proc for the
189
- # secret.
190
- return true if secret.is_a?(Proc)
191
-
192
- if secret.blank?
193
- raise ArgumentError, "A secret is required to generate an " +
194
- "integrity hash for cookie session data. Use " +
195
- "config.secret_token = \"some secret phrase of at " +
196
- "least #{SECRET_MIN_LENGTH} characters\"" +
197
- "in config/application.rb"
198
- end
199
-
200
- if secret.length < SECRET_MIN_LENGTH
201
- raise ArgumentError, "Secret should be something secure, " +
202
- "like \"#{ActiveSupport::SecureRandom.hex(16)}\". The value you " +
203
- "provided, \"#{secret}\", is shorter than the minimum length " +
204
- "of #{SECRET_MIN_LENGTH} characters"
205
- end
206
- end
207
-
208
- def verifier_for(secret, digest)
209
- key = secret.respond_to?(:call) ? secret.call : secret
210
- ActiveSupport::MessageVerifier.new(key, digest)
211
- end
212
-
213
- def generate_sid
214
- ActiveSupport::SecureRandom.hex(16)
215
- end
216
-
217
- def persistent_session_id!(data)
218
- (data ||= {}).merge!(inject_persistent_session_id(data))
73
+ def set_cookie(request, options)
74
+ request.cookie_jar.signed[@key] = options
219
75
  end
220
76
 
221
- def inject_persistent_session_id(data)
222
- requires_session_id?(data) ? { "session_id" => generate_sid } : {}
77
+ def set_session(env, sid, session_data)
78
+ persistent_session_id!(session_data, sid)
223
79
  end
224
80
 
225
- def requires_session_id?(data)
226
- if data
227
- data.respond_to?(:key?) && !data.key?("session_id")
228
- else
229
- true
230
- end
81
+ def persistent_session_id!(data, sid=nil)
82
+ data ||= {}
83
+ data["session_id"] ||= sid || generate_sid
84
+ data
231
85
  end
232
86
  end
233
87
  end