edgar-rack 1.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.
- data/COPYING +18 -0
- data/KNOWN-ISSUES +21 -0
- data/README +401 -0
- data/Rakefile +101 -0
- data/SPEC +171 -0
- data/bin/rackup +4 -0
- data/contrib/rack_logo.svg +111 -0
- data/example/lobster.ru +4 -0
- data/example/protectedlobster.rb +14 -0
- data/example/protectedlobster.ru +8 -0
- data/lib/rack.rb +81 -0
- data/lib/rack/auth/abstract/handler.rb +37 -0
- data/lib/rack/auth/abstract/request.rb +43 -0
- data/lib/rack/auth/basic.rb +58 -0
- data/lib/rack/auth/digest/md5.rb +124 -0
- data/lib/rack/auth/digest/nonce.rb +51 -0
- data/lib/rack/auth/digest/params.rb +53 -0
- data/lib/rack/auth/digest/request.rb +40 -0
- data/lib/rack/builder.rb +80 -0
- data/lib/rack/cascade.rb +41 -0
- data/lib/rack/chunked.rb +52 -0
- data/lib/rack/commonlogger.rb +49 -0
- data/lib/rack/conditionalget.rb +63 -0
- data/lib/rack/config.rb +15 -0
- data/lib/rack/content_length.rb +29 -0
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +96 -0
- data/lib/rack/directory.rb +157 -0
- data/lib/rack/etag.rb +59 -0
- data/lib/rack/file.rb +118 -0
- data/lib/rack/handler.rb +88 -0
- data/lib/rack/handler/cgi.rb +61 -0
- data/lib/rack/handler/evented_mongrel.rb +8 -0
- data/lib/rack/handler/fastcgi.rb +90 -0
- data/lib/rack/handler/lsws.rb +61 -0
- data/lib/rack/handler/mongrel.rb +90 -0
- data/lib/rack/handler/scgi.rb +59 -0
- data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/lib/rack/handler/thin.rb +17 -0
- data/lib/rack/handler/webrick.rb +73 -0
- data/lib/rack/head.rb +19 -0
- data/lib/rack/lint.rb +567 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/lock.rb +44 -0
- data/lib/rack/logger.rb +18 -0
- data/lib/rack/methodoverride.rb +27 -0
- data/lib/rack/mime.rb +210 -0
- data/lib/rack/mock.rb +185 -0
- data/lib/rack/nulllogger.rb +18 -0
- data/lib/rack/recursive.rb +61 -0
- data/lib/rack/reloader.rb +109 -0
- data/lib/rack/request.rb +307 -0
- data/lib/rack/response.rb +151 -0
- data/lib/rack/rewindable_input.rb +104 -0
- data/lib/rack/runtime.rb +27 -0
- data/lib/rack/sendfile.rb +139 -0
- data/lib/rack/server.rb +289 -0
- data/lib/rack/session/abstract/id.rb +348 -0
- data/lib/rack/session/cookie.rb +152 -0
- data/lib/rack/session/memcache.rb +93 -0
- data/lib/rack/session/pool.rb +79 -0
- data/lib/rack/showexceptions.rb +378 -0
- data/lib/rack/showstatus.rb +113 -0
- data/lib/rack/static.rb +53 -0
- data/lib/rack/urlmap.rb +55 -0
- data/lib/rack/utils.rb +698 -0
- data/rack.gemspec +39 -0
- data/test/cgi/lighttpd.conf +25 -0
- data/test/cgi/rackup_stub.rb +6 -0
- data/test/cgi/sample_rackup.ru +5 -0
- data/test/cgi/test +9 -0
- data/test/cgi/test.fcgi +8 -0
- data/test/cgi/test.ru +5 -0
- data/test/gemloader.rb +6 -0
- data/test/multipart/bad_robots +259 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/fail_16384_nofile +814 -0
- data/test/multipart/file1.txt +1 -0
- data/test/multipart/filename_and_modification_param +7 -0
- data/test/multipart/filename_with_escaped_quotes +6 -0
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
- data/test/multipart/filename_with_percent_escaped_quotes +6 -0
- data/test/multipart/filename_with_unescaped_quotes +6 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/semicolon +6 -0
- data/test/multipart/text +15 -0
- data/test/rackup/config.ru +31 -0
- data/test/spec_auth_basic.rb +70 -0
- data/test/spec_auth_digest.rb +241 -0
- data/test/spec_builder.rb +123 -0
- data/test/spec_cascade.rb +45 -0
- data/test/spec_cgi.rb +102 -0
- data/test/spec_chunked.rb +60 -0
- data/test/spec_commonlogger.rb +56 -0
- data/test/spec_conditionalget.rb +86 -0
- data/test/spec_config.rb +23 -0
- data/test/spec_content_length.rb +36 -0
- data/test/spec_content_type.rb +29 -0
- data/test/spec_deflater.rb +125 -0
- data/test/spec_directory.rb +57 -0
- data/test/spec_etag.rb +75 -0
- data/test/spec_fastcgi.rb +107 -0
- data/test/spec_file.rb +92 -0
- data/test/spec_handler.rb +49 -0
- data/test/spec_head.rb +30 -0
- data/test/spec_lint.rb +515 -0
- data/test/spec_lobster.rb +43 -0
- data/test/spec_lock.rb +142 -0
- data/test/spec_logger.rb +28 -0
- data/test/spec_methodoverride.rb +58 -0
- data/test/spec_mock.rb +241 -0
- data/test/spec_mongrel.rb +182 -0
- data/test/spec_nulllogger.rb +12 -0
- data/test/spec_recursive.rb +69 -0
- data/test/spec_request.rb +774 -0
- data/test/spec_response.rb +245 -0
- data/test/spec_rewindable_input.rb +118 -0
- data/test/spec_runtime.rb +39 -0
- data/test/spec_sendfile.rb +83 -0
- data/test/spec_server.rb +8 -0
- data/test/spec_session_abstract_id.rb +43 -0
- data/test/spec_session_cookie.rb +171 -0
- data/test/spec_session_memcache.rb +289 -0
- data/test/spec_session_pool.rb +200 -0
- data/test/spec_showexceptions.rb +87 -0
- data/test/spec_showstatus.rb +79 -0
- data/test/spec_static.rb +48 -0
- data/test/spec_thin.rb +86 -0
- data/test/spec_urlmap.rb +213 -0
- data/test/spec_utils.rb +678 -0
- data/test/spec_webrick.rb +141 -0
- data/test/testrequest.rb +78 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +329 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
|
|
2
|
+
# bugrep: Andreas Zehnder
|
|
3
|
+
|
|
4
|
+
require 'time'
|
|
5
|
+
require 'rack/request'
|
|
6
|
+
require 'rack/response'
|
|
7
|
+
begin
|
|
8
|
+
require 'securerandom'
|
|
9
|
+
rescue LoadError
|
|
10
|
+
# We just won't get securerandom
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module Rack
|
|
14
|
+
|
|
15
|
+
module Session
|
|
16
|
+
|
|
17
|
+
module Abstract
|
|
18
|
+
ENV_SESSION_KEY = 'rack.session'.freeze
|
|
19
|
+
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
|
|
20
|
+
|
|
21
|
+
# Thin wrapper around Hash that allows us to lazily load session id into session_options.
|
|
22
|
+
|
|
23
|
+
class OptionsHash < Hash #:nodoc:
|
|
24
|
+
def initialize(by, env, default_options)
|
|
25
|
+
@by = by
|
|
26
|
+
@env = env
|
|
27
|
+
@session_id_loaded = false
|
|
28
|
+
merge!(default_options)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def [](key)
|
|
32
|
+
load_session_id! if key == :id && session_id_not_loaded?
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def session_id_not_loaded?
|
|
39
|
+
!key?(:id) && !@session_id_loaded
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def load_session_id!
|
|
43
|
+
self[:id] = @by.send(:extract_session_id, @env)
|
|
44
|
+
@session_id_loaded = true
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# SessionHash is responsible to lazily load the session from store.
|
|
49
|
+
|
|
50
|
+
class SessionHash < Hash
|
|
51
|
+
def initialize(by, env)
|
|
52
|
+
super()
|
|
53
|
+
@by = by
|
|
54
|
+
@env = env
|
|
55
|
+
@loaded = false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def [](key)
|
|
59
|
+
load_for_read!
|
|
60
|
+
super(key.to_s)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def has_key?(key)
|
|
64
|
+
load_for_read!
|
|
65
|
+
super(key.to_s)
|
|
66
|
+
end
|
|
67
|
+
alias :key? :has_key?
|
|
68
|
+
alias :include? :has_key?
|
|
69
|
+
|
|
70
|
+
def []=(key, value)
|
|
71
|
+
load_for_write!
|
|
72
|
+
super(key.to_s, value)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def clear
|
|
76
|
+
load_for_write!
|
|
77
|
+
super
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def to_hash
|
|
81
|
+
load_for_read!
|
|
82
|
+
h = {}.replace(self)
|
|
83
|
+
h.delete_if { |k,v| v.nil? }
|
|
84
|
+
h
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def update(hash)
|
|
88
|
+
load_for_write!
|
|
89
|
+
super(stringify_keys(hash))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def delete(key)
|
|
93
|
+
load_for_write!
|
|
94
|
+
super(key.to_s)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def inspect
|
|
98
|
+
load_for_read!
|
|
99
|
+
super
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def exists?
|
|
103
|
+
return @exists if instance_variable_defined?(:@exists)
|
|
104
|
+
@exists = @by.send(:session_exists?, @env)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def loaded?
|
|
108
|
+
@loaded
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def load_for_read!
|
|
114
|
+
load! if !loaded? && exists?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def load_for_write!
|
|
118
|
+
load! unless loaded?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def load!
|
|
122
|
+
id, session = @by.send(:load_session, @env)
|
|
123
|
+
@env[ENV_SESSION_OPTIONS_KEY][:id] = id
|
|
124
|
+
replace(stringify_keys(session))
|
|
125
|
+
@loaded = true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def stringify_keys(other)
|
|
129
|
+
hash = {}
|
|
130
|
+
other.each do |key, value|
|
|
131
|
+
hash[key.to_s] = value
|
|
132
|
+
end
|
|
133
|
+
hash
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# ID sets up a basic framework for implementing an id based sessioning
|
|
138
|
+
# service. Cookies sent to the client for maintaining sessions will only
|
|
139
|
+
# contain an id reference. Only #get_session and #set_session are
|
|
140
|
+
# required to be overwritten.
|
|
141
|
+
#
|
|
142
|
+
# All parameters are optional.
|
|
143
|
+
# * :key determines the name of the cookie, by default it is
|
|
144
|
+
# 'rack.session'
|
|
145
|
+
# * :path, :domain, :expire_after, :secure, and :httponly set the related
|
|
146
|
+
# cookie options as by Rack::Response#add_cookie
|
|
147
|
+
# * :defer will not set a cookie in the response.
|
|
148
|
+
# * :renew (implementation dependent) will prompt the generation of a new
|
|
149
|
+
# session id, and migration of data to be referenced at the new id. If
|
|
150
|
+
# :defer is set, it will be overridden and the cookie will be set.
|
|
151
|
+
# * :sidbits sets the number of bits in length that a generated session
|
|
152
|
+
# id will be.
|
|
153
|
+
#
|
|
154
|
+
# These options can be set on a per request basis, at the location of
|
|
155
|
+
# env['rack.session.options']. Additionally the id of the session can be
|
|
156
|
+
# found within the options hash at the key :id. It is highly not
|
|
157
|
+
# recommended to change its value.
|
|
158
|
+
#
|
|
159
|
+
# Is Rack::Utils::Context compatible.
|
|
160
|
+
|
|
161
|
+
class ID
|
|
162
|
+
DEFAULT_OPTIONS = {
|
|
163
|
+
:key => 'rack.session',
|
|
164
|
+
:path => '/',
|
|
165
|
+
:domain => nil,
|
|
166
|
+
:expire_after => nil,
|
|
167
|
+
:secure => false,
|
|
168
|
+
:httponly => true,
|
|
169
|
+
:defer => false,
|
|
170
|
+
:renew => false,
|
|
171
|
+
:sidbits => 128,
|
|
172
|
+
:cookie_only => true,
|
|
173
|
+
:secure_random => begin ::SecureRandom rescue false end
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
attr_reader :key, :default_options
|
|
177
|
+
|
|
178
|
+
def initialize(app, options={})
|
|
179
|
+
@app = app
|
|
180
|
+
@default_options = self.class::DEFAULT_OPTIONS.merge(options)
|
|
181
|
+
@key = options[:key] || "rack.session"
|
|
182
|
+
@cookie_only = @default_options.delete(:cookie_only)
|
|
183
|
+
initialize_sid
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def call(env)
|
|
187
|
+
context(env)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def context(env, app=@app)
|
|
191
|
+
prepare_session(env)
|
|
192
|
+
status, headers, body = app.call(env)
|
|
193
|
+
commit_session(env, status, headers, body)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
private
|
|
197
|
+
|
|
198
|
+
def initialize_sid
|
|
199
|
+
sidbits = @default_options.delete(:sidbits)
|
|
200
|
+
@sid_secure = @default_options.delete(:secure_random)
|
|
201
|
+
@sid_template = "%0#{sidbits / 4}x"
|
|
202
|
+
@sid_rand_width = (2**sidbits - 1)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Generate a new session id using Ruby #rand. The size of the
|
|
206
|
+
# session id is controlled by the :sidbits option.
|
|
207
|
+
# Monkey patch this to use custom methods for session id generation.
|
|
208
|
+
|
|
209
|
+
def generate_sid
|
|
210
|
+
r = if @sid_secure
|
|
211
|
+
SecureRandom.random_number(@sid_rand_width)
|
|
212
|
+
else
|
|
213
|
+
Kernel.rand(@sid_rand_width)
|
|
214
|
+
end
|
|
215
|
+
@sid_template % r
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Sets the lazy session at 'rack.session' and places options and session
|
|
219
|
+
# metadata into 'rack.session.options'.
|
|
220
|
+
|
|
221
|
+
def prepare_session(env)
|
|
222
|
+
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
|
|
223
|
+
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Extracts the session id from provided cookies and passes it and the
|
|
227
|
+
# environment to #get_session.
|
|
228
|
+
|
|
229
|
+
def load_session(env)
|
|
230
|
+
sid = current_session_id(env)
|
|
231
|
+
sid, session = get_session(env, sid)
|
|
232
|
+
[sid, session || {}]
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Extract session id from request object.
|
|
236
|
+
|
|
237
|
+
def extract_session_id(env)
|
|
238
|
+
request = Rack::Request.new(env)
|
|
239
|
+
sid = request.cookies[@key]
|
|
240
|
+
sid ||= request.params[@key] unless @cookie_only
|
|
241
|
+
sid
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Returns the current session id from the OptionsHash.
|
|
245
|
+
|
|
246
|
+
def current_session_id(env)
|
|
247
|
+
env[ENV_SESSION_OPTIONS_KEY][:id]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Check if the session exists or not.
|
|
251
|
+
|
|
252
|
+
def session_exists?(env)
|
|
253
|
+
value = current_session_id(env)
|
|
254
|
+
value && !value.empty?
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Session should be commited if it was loaded, any of specific options like :renew, :drop
|
|
258
|
+
# or :expire_after was given and the security permissions match.
|
|
259
|
+
|
|
260
|
+
def commit_session?(env, session, options)
|
|
261
|
+
(loaded_session?(session) || force_options?(options)) && secure_session?(env, options)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def loaded_session?(session)
|
|
265
|
+
!session.is_a?(SessionHash) || session.loaded?
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def force_options?(options)
|
|
269
|
+
options.values_at(:renew, :drop, :defer, :expire_after).any?
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def secure_session?(env, options)
|
|
273
|
+
return true unless options[:secure]
|
|
274
|
+
request = Rack::Request.new(env)
|
|
275
|
+
request.ssl?
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Acquires the session from the environment and the session id from
|
|
279
|
+
# the session options and passes them to #set_session. If successful
|
|
280
|
+
# and the :defer option is not true, a cookie will be added to the
|
|
281
|
+
# response with the session's id.
|
|
282
|
+
|
|
283
|
+
def commit_session(env, status, headers, body)
|
|
284
|
+
session = env['rack.session']
|
|
285
|
+
options = env['rack.session.options']
|
|
286
|
+
|
|
287
|
+
if options[:drop] || options[:renew]
|
|
288
|
+
session_id = destroy_session(env, options[:id] || generate_sid, options)
|
|
289
|
+
return [status, headers, body] unless session_id
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
return [status, headers, body] unless commit_session?(env, session, options)
|
|
293
|
+
|
|
294
|
+
session.send(:load!) unless loaded_session?(session)
|
|
295
|
+
session = session.to_hash
|
|
296
|
+
session_id ||= options[:id] || generate_sid
|
|
297
|
+
|
|
298
|
+
if not data = set_session(env, session_id, session, options)
|
|
299
|
+
env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
|
|
300
|
+
elsif options[:defer] and not options[:renew]
|
|
301
|
+
env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
|
|
302
|
+
else
|
|
303
|
+
cookie = Hash.new
|
|
304
|
+
cookie[:value] = data
|
|
305
|
+
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
|
306
|
+
set_cookie(env, headers, cookie.merge!(options))
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
[status, headers, body]
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Sets the cookie back to the client with session id. We skip the cookie
|
|
313
|
+
# setting if the value didn't change (sid is the same) or expires was given.
|
|
314
|
+
|
|
315
|
+
def set_cookie(env, headers, cookie)
|
|
316
|
+
request = Rack::Request.new(env)
|
|
317
|
+
if request.cookies[@key] != cookie[:value] || cookie[:expires]
|
|
318
|
+
Utils.set_cookie_header!(headers, @key, cookie)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# All thread safety and session retrival proceedures should occur here.
|
|
323
|
+
# Should return [session_id, session].
|
|
324
|
+
# If nil is provided as the session id, generation of a new valid id
|
|
325
|
+
# should occur within.
|
|
326
|
+
|
|
327
|
+
def get_session(env, sid)
|
|
328
|
+
raise '#get_session not implemented.'
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# All thread safety and session storage proceedures should occur here.
|
|
332
|
+
# Should return true or false dependant on whether or not the session
|
|
333
|
+
# was saved or not.
|
|
334
|
+
|
|
335
|
+
def set_session(env, sid, session, options)
|
|
336
|
+
raise '#set_session not implemented.'
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# All thread safety and session destroy proceedures should occur here.
|
|
340
|
+
# Should return a new session id or nil if options[:drop]
|
|
341
|
+
|
|
342
|
+
def destroy_session(env, sid, options)
|
|
343
|
+
raise '#destroy_session not implemented'
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require 'openssl'
|
|
2
|
+
require 'rack/request'
|
|
3
|
+
require 'rack/response'
|
|
4
|
+
require 'rack/session/abstract/id'
|
|
5
|
+
|
|
6
|
+
module Rack
|
|
7
|
+
|
|
8
|
+
module Session
|
|
9
|
+
|
|
10
|
+
# Rack::Session::Cookie provides simple cookie based session management.
|
|
11
|
+
# By default, the session is a Ruby Hash stored as base64 encoded marshalled
|
|
12
|
+
# data set to :key (default: rack.session). The object that encodes the
|
|
13
|
+
# session data is configurable and must respond to +encode+ and +decode+.
|
|
14
|
+
# Both methods must take a string and return a string.
|
|
15
|
+
#
|
|
16
|
+
# When the secret key is set, cookie data is checked for data integrity.
|
|
17
|
+
#
|
|
18
|
+
# Example:
|
|
19
|
+
#
|
|
20
|
+
# use Rack::Session::Cookie, :key => 'rack.session',
|
|
21
|
+
# :domain => 'foo.com',
|
|
22
|
+
# :path => '/',
|
|
23
|
+
# :expire_after => 2592000,
|
|
24
|
+
# :secret => 'change_me'
|
|
25
|
+
#
|
|
26
|
+
# All parameters are optional.
|
|
27
|
+
#
|
|
28
|
+
# Example of a cookie with no encoding:
|
|
29
|
+
#
|
|
30
|
+
# Rack::Session::Cookie.new(application, {
|
|
31
|
+
# :coder => Racke::Session::Cookie::Identity.new
|
|
32
|
+
# })
|
|
33
|
+
#
|
|
34
|
+
# Example of a cookie with custom encoding:
|
|
35
|
+
#
|
|
36
|
+
# Rack::Session::Cookie.new(application, {
|
|
37
|
+
# :coder => Class.new {
|
|
38
|
+
# def encode(str); str.reverse; end
|
|
39
|
+
# def decode(str); str.reverse; end
|
|
40
|
+
# }.new
|
|
41
|
+
# })
|
|
42
|
+
#
|
|
43
|
+
|
|
44
|
+
class Cookie < Abstract::ID
|
|
45
|
+
# Encode session cookies as Base64
|
|
46
|
+
class Base64
|
|
47
|
+
def encode(str)
|
|
48
|
+
[str].pack('m')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def decode(str)
|
|
52
|
+
str.unpack('m').first
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Encode session cookies as Marshaled Base64 data
|
|
56
|
+
class Marshal < Base64
|
|
57
|
+
def encode(str)
|
|
58
|
+
super(::Marshal.dump(str))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def decode(str)
|
|
62
|
+
::Marshal.load(super(str)) rescue nil
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Use no encoding for session cookies
|
|
68
|
+
class Identity
|
|
69
|
+
def encode(str); str; end
|
|
70
|
+
def decode(str); str; end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Reverse string encoding. (trollface)
|
|
74
|
+
class Reverse
|
|
75
|
+
def encode(str); str.reverse; end
|
|
76
|
+
def decode(str); str.reverse; end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
attr_reader :coder
|
|
80
|
+
|
|
81
|
+
def initialize(app, options={})
|
|
82
|
+
@secret = options.delete(:secret)
|
|
83
|
+
@coder = options.delete(:coder) || Base64::Marshal.new
|
|
84
|
+
super(app, options.merge!(:cookie_only => true))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def load_session(env)
|
|
90
|
+
data = unpacked_cookie_data(env)
|
|
91
|
+
data = persistent_session_id!(data)
|
|
92
|
+
[data["session_id"], data]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def extract_session_id(env)
|
|
96
|
+
unpacked_cookie_data(env)["session_id"]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def unpacked_cookie_data(env)
|
|
100
|
+
env["rack.session.unpacked_cookie_data"] ||= begin
|
|
101
|
+
request = Rack::Request.new(env)
|
|
102
|
+
session_data = request.cookies[@key]
|
|
103
|
+
|
|
104
|
+
if @secret && session_data
|
|
105
|
+
session_data, digest = session_data.split("--")
|
|
106
|
+
session_data = nil unless digest == generate_hmac(session_data)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
coder.decode(session_data) || {}
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def persistent_session_id!(data, sid=nil)
|
|
114
|
+
data ||= {}
|
|
115
|
+
data["session_id"] ||= sid || generate_sid
|
|
116
|
+
data
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Overwrite set cookie to bypass content equality and always stream the cookie.
|
|
120
|
+
|
|
121
|
+
def set_cookie(env, headers, cookie)
|
|
122
|
+
Utils.set_cookie_header!(headers, @key, cookie)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def set_session(env, session_id, session, options)
|
|
126
|
+
session = persistent_session_id!(session, session_id)
|
|
127
|
+
session_data = coder.encode(session)
|
|
128
|
+
|
|
129
|
+
if @secret
|
|
130
|
+
session_data = "#{session_data}--#{generate_hmac(session_data)}"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
if session_data.size > (4096 - @key.size)
|
|
134
|
+
env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
|
|
135
|
+
nil
|
|
136
|
+
else
|
|
137
|
+
session_data
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def destroy_session(env, session_id, options)
|
|
142
|
+
# Nothing to do here, data is in the client
|
|
143
|
+
generate_sid unless options[:drop]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def generate_hmac(data)
|
|
147
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|