halorgium-actionpack 3.0.pre
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.
- data/CHANGELOG +5179 -0
- data/MIT-LICENSE +21 -0
- data/README +409 -0
- data/lib/abstract_controller.rb +16 -0
- data/lib/abstract_controller/base.rb +158 -0
- data/lib/abstract_controller/callbacks.rb +113 -0
- data/lib/abstract_controller/exceptions.rb +12 -0
- data/lib/abstract_controller/helpers.rb +151 -0
- data/lib/abstract_controller/layouts.rb +250 -0
- data/lib/abstract_controller/localized_cache.rb +49 -0
- data/lib/abstract_controller/logger.rb +61 -0
- data/lib/abstract_controller/rendering_controller.rb +188 -0
- data/lib/action_controller.rb +72 -0
- data/lib/action_controller/base.rb +168 -0
- data/lib/action_controller/caching.rb +80 -0
- data/lib/action_controller/caching/actions.rb +163 -0
- data/lib/action_controller/caching/fragments.rb +116 -0
- data/lib/action_controller/caching/pages.rb +154 -0
- data/lib/action_controller/caching/sweeping.rb +97 -0
- data/lib/action_controller/deprecated.rb +4 -0
- data/lib/action_controller/deprecated/integration_test.rb +2 -0
- data/lib/action_controller/deprecated/performance_test.rb +1 -0
- data/lib/action_controller/dispatch/dispatcher.rb +57 -0
- data/lib/action_controller/metal.rb +129 -0
- data/lib/action_controller/metal/benchmarking.rb +73 -0
- data/lib/action_controller/metal/compatibility.rb +145 -0
- data/lib/action_controller/metal/conditional_get.rb +86 -0
- data/lib/action_controller/metal/configuration.rb +28 -0
- data/lib/action_controller/metal/cookies.rb +105 -0
- data/lib/action_controller/metal/exceptions.rb +55 -0
- data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
- data/lib/action_controller/metal/flash.rb +162 -0
- data/lib/action_controller/metal/head.rb +27 -0
- data/lib/action_controller/metal/helpers.rb +115 -0
- data/lib/action_controller/metal/hide_actions.rb +47 -0
- data/lib/action_controller/metal/http_authentication.rb +312 -0
- data/lib/action_controller/metal/layouts.rb +171 -0
- data/lib/action_controller/metal/mime_responds.rb +317 -0
- data/lib/action_controller/metal/rack_convenience.rb +27 -0
- data/lib/action_controller/metal/redirector.rb +22 -0
- data/lib/action_controller/metal/render_options.rb +103 -0
- data/lib/action_controller/metal/rendering_controller.rb +57 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
- data/lib/action_controller/metal/rescuable.rb +13 -0
- data/lib/action_controller/metal/responder.rb +200 -0
- data/lib/action_controller/metal/session.rb +15 -0
- data/lib/action_controller/metal/session_management.rb +45 -0
- data/lib/action_controller/metal/streaming.rb +188 -0
- data/lib/action_controller/metal/testing.rb +39 -0
- data/lib/action_controller/metal/url_for.rb +41 -0
- data/lib/action_controller/metal/verification.rb +130 -0
- data/lib/action_controller/middleware.rb +38 -0
- data/lib/action_controller/notifications.rb +10 -0
- data/lib/action_controller/polymorphic_routes.rb +183 -0
- data/lib/action_controller/record_identifier.rb +91 -0
- data/lib/action_controller/testing/process.rb +111 -0
- data/lib/action_controller/testing/test_case.rb +345 -0
- data/lib/action_controller/translation.rb +13 -0
- data/lib/action_controller/url_rewriter.rb +204 -0
- data/lib/action_controller/vendor/html-scanner.rb +16 -0
- data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
- data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
- data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
- data/lib/action_dispatch.rb +70 -0
- data/lib/action_dispatch/http/headers.rb +33 -0
- data/lib/action_dispatch/http/mime_type.rb +231 -0
- data/lib/action_dispatch/http/mime_types.rb +23 -0
- data/lib/action_dispatch/http/request.rb +539 -0
- data/lib/action_dispatch/http/response.rb +290 -0
- data/lib/action_dispatch/http/status_codes.rb +42 -0
- data/lib/action_dispatch/http/utils.rb +20 -0
- data/lib/action_dispatch/middleware/callbacks.rb +50 -0
- data/lib/action_dispatch/middleware/params_parser.rb +79 -0
- data/lib/action_dispatch/middleware/rescue.rb +26 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
- data/lib/action_dispatch/middleware/stack.rb +116 -0
- data/lib/action_dispatch/middleware/static.rb +44 -0
- data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
- data/lib/action_dispatch/routing.rb +381 -0
- data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
- data/lib/action_dispatch/routing/mapper.rb +327 -0
- data/lib/action_dispatch/routing/route.rb +49 -0
- data/lib/action_dispatch/routing/route_set.rb +497 -0
- data/lib/action_dispatch/testing/assertions.rb +8 -0
- data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
- data/lib/action_dispatch/testing/assertions/model.rb +19 -0
- data/lib/action_dispatch/testing/assertions/response.rb +145 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
- data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
- data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
- data/lib/action_dispatch/testing/integration.rb +504 -0
- data/lib/action_dispatch/testing/performance_test.rb +15 -0
- data/lib/action_dispatch/testing/test_request.rb +83 -0
- data/lib/action_dispatch/testing/test_response.rb +131 -0
- data/lib/action_pack.rb +24 -0
- data/lib/action_pack/version.rb +9 -0
- data/lib/action_view.rb +58 -0
- data/lib/action_view/base.rb +308 -0
- data/lib/action_view/context.rb +44 -0
- data/lib/action_view/erb/util.rb +48 -0
- data/lib/action_view/helpers.rb +62 -0
- data/lib/action_view/helpers/active_model_helper.rb +306 -0
- data/lib/action_view/helpers/ajax_helper.rb +68 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
- data/lib/action_view/helpers/cache_helper.rb +39 -0
- data/lib/action_view/helpers/capture_helper.rb +168 -0
- data/lib/action_view/helpers/date_helper.rb +988 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +1102 -0
- data/lib/action_view/helpers/form_options_helper.rb +600 -0
- data/lib/action_view/helpers/form_tag_helper.rb +495 -0
- data/lib/action_view/helpers/javascript_helper.rb +208 -0
- data/lib/action_view/helpers/number_helper.rb +311 -0
- data/lib/action_view/helpers/prototype_helper.rb +1309 -0
- data/lib/action_view/helpers/raw_output_helper.rb +9 -0
- data/lib/action_view/helpers/record_identification_helper.rb +20 -0
- data/lib/action_view/helpers/record_tag_helper.rb +58 -0
- data/lib/action_view/helpers/sanitize_helper.rb +259 -0
- data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
- data/lib/action_view/helpers/tag_helper.rb +151 -0
- data/lib/action_view/helpers/text_helper.rb +594 -0
- data/lib/action_view/helpers/translation_helper.rb +39 -0
- data/lib/action_view/helpers/url_helper.rb +639 -0
- data/lib/action_view/locale/en.yml +117 -0
- data/lib/action_view/paths.rb +80 -0
- data/lib/action_view/render/partials.rb +342 -0
- data/lib/action_view/render/rendering.rb +134 -0
- data/lib/action_view/safe_buffer.rb +28 -0
- data/lib/action_view/template/error.rb +101 -0
- data/lib/action_view/template/handler.rb +36 -0
- data/lib/action_view/template/handlers.rb +52 -0
- data/lib/action_view/template/handlers/builder.rb +17 -0
- data/lib/action_view/template/handlers/erb.rb +53 -0
- data/lib/action_view/template/handlers/rjs.rb +18 -0
- data/lib/action_view/template/resolver.rb +165 -0
- data/lib/action_view/template/template.rb +131 -0
- data/lib/action_view/template/text.rb +38 -0
- data/lib/action_view/test_case.rb +163 -0
- metadata +236 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module ActionDispatch
|
|
2
|
+
class Rescue
|
|
3
|
+
def initialize(app, rescuers = {}, &block)
|
|
4
|
+
@app, @rescuers = app, {}
|
|
5
|
+
rescuers.each { |exception, rescuer| rescue_from(exception, rescuer) }
|
|
6
|
+
instance_eval(&block) if block_given?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call(env)
|
|
10
|
+
@app.call(env)
|
|
11
|
+
rescue Exception => exception
|
|
12
|
+
if rescuer = @rescuers[exception.class.name]
|
|
13
|
+
env['action_dispatch.rescue.exception'] = exception
|
|
14
|
+
rescuer.call(env)
|
|
15
|
+
else
|
|
16
|
+
raise exception
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
protected
|
|
21
|
+
def rescue_from(exception, rescuer)
|
|
22
|
+
exception = exception.class.name if exception.is_a?(Exception)
|
|
23
|
+
@rescuers[exception.to_s] = rescuer
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
require 'rack/utils'
|
|
2
|
+
|
|
3
|
+
module ActionDispatch
|
|
4
|
+
module Session
|
|
5
|
+
class SessionRestoreError < StandardError #:nodoc:
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class AbstractStore
|
|
9
|
+
ENV_SESSION_KEY = 'rack.session'.freeze
|
|
10
|
+
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
|
|
11
|
+
|
|
12
|
+
HTTP_COOKIE = 'HTTP_COOKIE'.freeze
|
|
13
|
+
SET_COOKIE = 'Set-Cookie'.freeze
|
|
14
|
+
|
|
15
|
+
class SessionHash < Hash
|
|
16
|
+
def initialize(by, env)
|
|
17
|
+
super()
|
|
18
|
+
@by = by
|
|
19
|
+
@env = env
|
|
20
|
+
@loaded = false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def session_id
|
|
24
|
+
ActiveSupport::Deprecation.warn(
|
|
25
|
+
"ActionDispatch::Session::AbstractStore::SessionHash#session_id " +
|
|
26
|
+
"has been deprecated. Please use request.session_options[:id] instead.", caller)
|
|
27
|
+
@env[ENV_SESSION_OPTIONS_KEY][:id]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def [](key)
|
|
31
|
+
load! unless @loaded
|
|
32
|
+
super(key.to_s)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def []=(key, value)
|
|
36
|
+
load! unless @loaded
|
|
37
|
+
super(key.to_s, value)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_hash
|
|
41
|
+
h = {}.replace(self)
|
|
42
|
+
h.delete_if { |k,v| v.nil? }
|
|
43
|
+
h
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def update(hash = nil)
|
|
47
|
+
if hash.nil?
|
|
48
|
+
ActiveSupport::Deprecation.warn('use replace instead', caller)
|
|
49
|
+
replace({})
|
|
50
|
+
else
|
|
51
|
+
load! unless @loaded
|
|
52
|
+
super(hash.stringify_keys)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def delete(key = nil)
|
|
57
|
+
if key.nil?
|
|
58
|
+
ActiveSupport::Deprecation.warn('use clear instead', caller)
|
|
59
|
+
clear
|
|
60
|
+
else
|
|
61
|
+
load! unless @loaded
|
|
62
|
+
super(key.to_s)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def data
|
|
67
|
+
ActiveSupport::Deprecation.warn(
|
|
68
|
+
"ActionDispatch::Session::AbstractStore::SessionHash#data " +
|
|
69
|
+
"has been deprecated. Please use #to_hash instead.", caller)
|
|
70
|
+
to_hash
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def close
|
|
74
|
+
ActiveSupport::Deprecation.warn('sessions should no longer be closed', caller)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def inspect
|
|
78
|
+
load! unless @loaded
|
|
79
|
+
super
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
def loaded?
|
|
84
|
+
@loaded
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def load!
|
|
88
|
+
stale_session_check! do
|
|
89
|
+
id, session = @by.send(:load_session, @env)
|
|
90
|
+
(@env[ENV_SESSION_OPTIONS_KEY] ||= {})[:id] = id
|
|
91
|
+
replace(session.stringify_keys)
|
|
92
|
+
@loaded = true
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def stale_session_check!
|
|
97
|
+
yield
|
|
98
|
+
rescue ArgumentError => argument_error
|
|
99
|
+
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
|
100
|
+
begin
|
|
101
|
+
# Note that the regexp does not allow $1 to end with a ':'
|
|
102
|
+
$1.constantize
|
|
103
|
+
rescue LoadError, NameError => const_error
|
|
104
|
+
raise ActionDispatch::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
retry
|
|
108
|
+
else
|
|
109
|
+
raise
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
DEFAULT_OPTIONS = {
|
|
115
|
+
:key => '_session_id',
|
|
116
|
+
:path => '/',
|
|
117
|
+
:domain => nil,
|
|
118
|
+
:expire_after => nil,
|
|
119
|
+
:secure => false,
|
|
120
|
+
:httponly => true,
|
|
121
|
+
:cookie_only => true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
def initialize(app, options = {})
|
|
125
|
+
# Process legacy CGI options
|
|
126
|
+
options = options.symbolize_keys
|
|
127
|
+
if options.has_key?(:session_path)
|
|
128
|
+
options[:path] = options.delete(:session_path)
|
|
129
|
+
end
|
|
130
|
+
if options.has_key?(:session_key)
|
|
131
|
+
options[:key] = options.delete(:session_key)
|
|
132
|
+
end
|
|
133
|
+
if options.has_key?(:session_http_only)
|
|
134
|
+
options[:httponly] = options.delete(:session_http_only)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
@app = app
|
|
138
|
+
@default_options = DEFAULT_OPTIONS.merge(options)
|
|
139
|
+
@key = @default_options[:key]
|
|
140
|
+
@cookie_only = @default_options[:cookie_only]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def call(env)
|
|
144
|
+
session = SessionHash.new(self, env)
|
|
145
|
+
|
|
146
|
+
env[ENV_SESSION_KEY] = session
|
|
147
|
+
env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
|
|
148
|
+
|
|
149
|
+
response = @app.call(env)
|
|
150
|
+
|
|
151
|
+
session_data = env[ENV_SESSION_KEY]
|
|
152
|
+
options = env[ENV_SESSION_OPTIONS_KEY]
|
|
153
|
+
|
|
154
|
+
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
|
|
155
|
+
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
|
|
156
|
+
|
|
157
|
+
sid = options[:id] || generate_sid
|
|
158
|
+
|
|
159
|
+
unless set_session(env, sid, session_data.to_hash)
|
|
160
|
+
return response
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
cookie = Rack::Utils.escape(@key) + '=' + Rack::Utils.escape(sid)
|
|
164
|
+
cookie << "; domain=#{options[:domain]}" if options[:domain]
|
|
165
|
+
cookie << "; path=#{options[:path]}" if options[:path]
|
|
166
|
+
if options[:expire_after]
|
|
167
|
+
expiry = Time.now + options[:expire_after]
|
|
168
|
+
cookie << "; expires=#{expiry.httpdate}"
|
|
169
|
+
end
|
|
170
|
+
cookie << "; Secure" if options[:secure]
|
|
171
|
+
cookie << "; HttpOnly" if options[:httponly]
|
|
172
|
+
|
|
173
|
+
headers = response[1]
|
|
174
|
+
unless headers[SET_COOKIE].blank?
|
|
175
|
+
headers[SET_COOKIE] << "\n#{cookie}"
|
|
176
|
+
else
|
|
177
|
+
headers[SET_COOKIE] = cookie
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
response
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
def generate_sid
|
|
186
|
+
ActiveSupport::SecureRandom.hex(16)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def load_session(env)
|
|
190
|
+
request = Rack::Request.new(env)
|
|
191
|
+
sid = request.cookies[@key]
|
|
192
|
+
unless @cookie_only
|
|
193
|
+
sid ||= request.params[@key]
|
|
194
|
+
end
|
|
195
|
+
sid, session = get_session(env, sid)
|
|
196
|
+
[sid, session]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def get_session(env, sid)
|
|
200
|
+
raise '#get_session needs to be implemented.'
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def set_session(env, sid, session_data)
|
|
204
|
+
raise '#set_session needs to be implemented.'
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
require "active_support/core_ext/hash/keys"
|
|
2
|
+
|
|
3
|
+
module ActionDispatch
|
|
4
|
+
module Session
|
|
5
|
+
# This cookie-based session store is the Rails default. Sessions typically
|
|
6
|
+
# contain at most a user_id and flash message; both fit within the 4K cookie
|
|
7
|
+
# size limit. Cookie-based sessions are dramatically faster than the
|
|
8
|
+
# alternatives.
|
|
9
|
+
#
|
|
10
|
+
# If you have more than 4K of session data or don't want your data to be
|
|
11
|
+
# visible to the user, pick another session store.
|
|
12
|
+
#
|
|
13
|
+
# CookieOverflow is raised if you attempt to store more than 4K of data.
|
|
14
|
+
#
|
|
15
|
+
# A message digest is included with the cookie to ensure data integrity:
|
|
16
|
+
# a user cannot alter his +user_id+ without knowing the secret key
|
|
17
|
+
# included in the hash. New apps are generated with a pregenerated secret
|
|
18
|
+
# in config/environment.rb. Set your own for old apps you're upgrading.
|
|
19
|
+
#
|
|
20
|
+
# Session options:
|
|
21
|
+
#
|
|
22
|
+
# * <tt>:secret</tt>: An application-wide key string or block returning a
|
|
23
|
+
# string called per generated digest. The block is called with the
|
|
24
|
+
# CGI::Session instance as an argument. It's important that the secret
|
|
25
|
+
# is not vulnerable to a dictionary attack. Therefore, you should choose
|
|
26
|
+
# a secret consisting of random numbers and letters and more than 30
|
|
27
|
+
# characters. Examples:
|
|
28
|
+
#
|
|
29
|
+
# :secret => '449fe2e7daee471bffae2fd8dc02313d'
|
|
30
|
+
# :secret => Proc.new { User.current_user.secret_key }
|
|
31
|
+
#
|
|
32
|
+
# * <tt>:digest</tt>: The message digest algorithm used to verify session
|
|
33
|
+
# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
|
|
34
|
+
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
|
|
35
|
+
#
|
|
36
|
+
# To generate a secret key for an existing application, run
|
|
37
|
+
# "rake secret" and set the key in config/environment.rb.
|
|
38
|
+
#
|
|
39
|
+
# Note that changing digest or secret invalidates all existing sessions!
|
|
40
|
+
class CookieStore
|
|
41
|
+
# Cookies can typically store 4096 bytes.
|
|
42
|
+
MAX = 4096
|
|
43
|
+
SECRET_MIN_LENGTH = 30 # characters
|
|
44
|
+
|
|
45
|
+
DEFAULT_OPTIONS = {
|
|
46
|
+
:key => '_session_id',
|
|
47
|
+
:domain => nil,
|
|
48
|
+
:path => "/",
|
|
49
|
+
:expire_after => nil,
|
|
50
|
+
:httponly => true
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
53
|
+
class OptionsHash < Hash
|
|
54
|
+
def initialize(by, env, default_options)
|
|
55
|
+
@session_data = env[CookieStore::ENV_SESSION_KEY]
|
|
56
|
+
default_options.each { |key, value| self[key] = value }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def [](key)
|
|
60
|
+
key == :id ? @session_data[:session_id] : super(key)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
ENV_SESSION_KEY = "rack.session".freeze
|
|
65
|
+
ENV_SESSION_OPTIONS_KEY = "rack.session.options".freeze
|
|
66
|
+
HTTP_SET_COOKIE = "Set-Cookie".freeze
|
|
67
|
+
|
|
68
|
+
# Raised when storing more than 4K of session data.
|
|
69
|
+
class CookieOverflow < StandardError; end
|
|
70
|
+
|
|
71
|
+
def initialize(app, options = {})
|
|
72
|
+
# Process legacy CGI options
|
|
73
|
+
options = options.symbolize_keys
|
|
74
|
+
if options.has_key?(:session_path)
|
|
75
|
+
options[:path] = options.delete(:session_path)
|
|
76
|
+
end
|
|
77
|
+
if options.has_key?(:session_key)
|
|
78
|
+
options[:key] = options.delete(:session_key)
|
|
79
|
+
end
|
|
80
|
+
if options.has_key?(:session_http_only)
|
|
81
|
+
options[:httponly] = options.delete(:session_http_only)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
@app = app
|
|
85
|
+
|
|
86
|
+
# The session_key option is required.
|
|
87
|
+
ensure_session_key(options[:key])
|
|
88
|
+
@key = options.delete(:key).freeze
|
|
89
|
+
|
|
90
|
+
# The secret option is required.
|
|
91
|
+
ensure_secret_secure(options[:secret])
|
|
92
|
+
@secret = options.delete(:secret).freeze
|
|
93
|
+
|
|
94
|
+
@digest = options.delete(:digest) || 'SHA1'
|
|
95
|
+
@verifier = verifier_for(@secret, @digest)
|
|
96
|
+
|
|
97
|
+
@default_options = DEFAULT_OPTIONS.merge(options).freeze
|
|
98
|
+
|
|
99
|
+
freeze
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def call(env)
|
|
103
|
+
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
|
|
104
|
+
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
|
|
105
|
+
|
|
106
|
+
status, headers, body = @app.call(env)
|
|
107
|
+
|
|
108
|
+
session_data = env[ENV_SESSION_KEY]
|
|
109
|
+
options = env[ENV_SESSION_OPTIONS_KEY]
|
|
110
|
+
|
|
111
|
+
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
|
|
112
|
+
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
|
|
113
|
+
session_data = marshal(session_data.to_hash)
|
|
114
|
+
|
|
115
|
+
raise CookieOverflow if session_data.size > MAX
|
|
116
|
+
|
|
117
|
+
cookie = Hash.new
|
|
118
|
+
cookie[:value] = session_data
|
|
119
|
+
unless options[:expire_after].nil?
|
|
120
|
+
cookie[:expires] = Time.now + options[:expire_after]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
cookie = build_cookie(@key, cookie.merge(options))
|
|
124
|
+
unless headers[HTTP_SET_COOKIE].blank?
|
|
125
|
+
headers[HTTP_SET_COOKIE] << "\n#{cookie}"
|
|
126
|
+
else
|
|
127
|
+
headers[HTTP_SET_COOKIE] = cookie
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
[status, headers, body]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
# Should be in Rack::Utils soon
|
|
136
|
+
def build_cookie(key, value)
|
|
137
|
+
case value
|
|
138
|
+
when Hash
|
|
139
|
+
domain = "; domain=" + value[:domain] if value[:domain]
|
|
140
|
+
path = "; path=" + value[:path] if value[:path]
|
|
141
|
+
# According to RFC 2109, we need dashes here.
|
|
142
|
+
# N.B.: cgi.rb uses spaces...
|
|
143
|
+
expires = "; expires=" + value[:expires].clone.gmtime.
|
|
144
|
+
strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
|
|
145
|
+
secure = "; secure" if value[:secure]
|
|
146
|
+
httponly = "; HttpOnly" if value[:httponly]
|
|
147
|
+
value = value[:value]
|
|
148
|
+
end
|
|
149
|
+
value = [value] unless Array === value
|
|
150
|
+
cookie = Rack::Utils.escape(key) + "=" +
|
|
151
|
+
value.map { |v| Rack::Utils.escape(v) }.join("&") +
|
|
152
|
+
"#{domain}#{path}#{expires}#{secure}#{httponly}"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def load_session(env)
|
|
156
|
+
request = Rack::Request.new(env)
|
|
157
|
+
session_data = request.cookies[@key]
|
|
158
|
+
data = unmarshal(session_data) || persistent_session_id!({})
|
|
159
|
+
data.stringify_keys!
|
|
160
|
+
[data["session_id"], data]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Marshal a session hash into safe cookie data. Include an integrity hash.
|
|
164
|
+
def marshal(session)
|
|
165
|
+
@verifier.generate(persistent_session_id!(session))
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Unmarshal cookie data to a hash and verify its integrity.
|
|
169
|
+
def unmarshal(cookie)
|
|
170
|
+
persistent_session_id!(@verifier.verify(cookie)) if cookie
|
|
171
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
172
|
+
nil
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def ensure_session_key(key)
|
|
176
|
+
if key.blank?
|
|
177
|
+
raise ArgumentError, 'A key is required to write a ' +
|
|
178
|
+
'cookie containing the session data. Use ' +
|
|
179
|
+
'config.action_controller.session = { :key => ' +
|
|
180
|
+
'"_myapp_session", :secret => "some secret phrase" } in ' +
|
|
181
|
+
'config/environment.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.action_controller.session = { :key => " +
|
|
196
|
+
"\"_myapp_session\", :secret => \"some secret phrase of at " +
|
|
197
|
+
"least #{SECRET_MIN_LENGTH} characters\" } " +
|
|
198
|
+
"in config/environment.rb"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if secret.length < SECRET_MIN_LENGTH
|
|
202
|
+
raise ArgumentError, "Secret should be something secure, " +
|
|
203
|
+
"like \"#{ActiveSupport::SecureRandom.hex(16)}\". The value you " +
|
|
204
|
+
"provided, \"#{secret}\", is shorter than the minimum length " +
|
|
205
|
+
"of #{SECRET_MIN_LENGTH} characters"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def verifier_for(secret, digest)
|
|
210
|
+
key = secret.respond_to?(:call) ? secret.call : secret
|
|
211
|
+
ActiveSupport::MessageVerifier.new(key, digest)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def generate_sid
|
|
215
|
+
ActiveSupport::SecureRandom.hex(16)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def persistent_session_id!(data)
|
|
219
|
+
(data ||= {}).merge!(inject_persistent_session_id(data))
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def inject_persistent_session_id(data)
|
|
223
|
+
requires_session_id?(data) ? { "session_id" => generate_sid } : {}
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def requires_session_id?(data)
|
|
227
|
+
if data
|
|
228
|
+
data.respond_to?(:key?) && !data.key?("session_id")
|
|
229
|
+
else
|
|
230
|
+
true
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|