rack 1.1.6 → 1.6.9
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.
- checksums.yaml +7 -0
- data/COPYING +1 -1
- data/HISTORY.md +375 -0
- data/KNOWN-ISSUES +23 -0
- data/README.rdoc +312 -0
- data/Rakefile +124 -0
- data/SPEC +125 -32
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +150 -0
- data/contrib/rack_logo.svg +1 -1
- data/contrib/rdoc.css +412 -0
- data/example/protectedlobster.rb +1 -1
- data/lib/rack/auth/abstract/handler.rb +4 -4
- data/lib/rack/auth/abstract/request.rb +7 -5
- data/lib/rack/auth/basic.rb +1 -1
- data/lib/rack/auth/digest/md5.rb +7 -3
- data/lib/rack/auth/digest/nonce.rb +1 -1
- data/lib/rack/auth/digest/params.rb +7 -9
- data/lib/rack/auth/digest/request.rb +10 -9
- data/lib/rack/backports/uri/common_18.rb +56 -0
- data/lib/rack/backports/uri/common_192.rb +52 -0
- data/lib/rack/backports/uri/common_193.rb +29 -0
- data/lib/rack/body_proxy.rb +39 -0
- data/lib/rack/builder.rb +106 -22
- data/lib/rack/cascade.rb +17 -6
- data/lib/rack/chunked.rb +44 -24
- data/lib/rack/commonlogger.rb +36 -13
- data/lib/rack/conditionalget.rb +49 -17
- data/lib/rack/config.rb +5 -0
- data/lib/rack/content_length.rb +14 -6
- data/lib/rack/content_type.rb +7 -1
- data/lib/rack/deflater.rb +73 -15
- data/lib/rack/directory.rb +18 -8
- data/lib/rack/etag.rb +59 -9
- data/lib/rack/file.rb +106 -44
- data/lib/rack/handler/cgi.rb +11 -11
- data/lib/rack/handler/fastcgi.rb +18 -6
- data/lib/rack/handler/lsws.rb +2 -4
- data/lib/rack/handler/mongrel.rb +22 -6
- data/lib/rack/handler/scgi.rb +16 -8
- data/lib/rack/handler/thin.rb +19 -4
- data/lib/rack/handler/webrick.rb +72 -19
- data/lib/rack/handler.rb +47 -14
- data/lib/rack/head.rb +10 -2
- data/lib/rack/lint.rb +260 -75
- data/lib/rack/lobster.rb +13 -8
- data/lib/rack/lock.rb +13 -3
- data/lib/rack/logger.rb +0 -2
- data/lib/rack/methodoverride.rb +27 -8
- data/lib/rack/mime.rb +625 -167
- data/lib/rack/mock.rb +78 -53
- data/lib/rack/multipart/generator.rb +93 -0
- data/lib/rack/multipart/parser.rb +253 -0
- data/lib/rack/multipart/uploaded_file.rb +34 -0
- data/lib/rack/multipart.rb +34 -0
- data/lib/rack/nulllogger.rb +21 -2
- data/lib/rack/recursive.rb +10 -5
- data/lib/rack/reloader.rb +3 -2
- data/lib/rack/request.rb +201 -74
- data/lib/rack/response.rb +41 -28
- data/lib/rack/rewindable_input.rb +15 -11
- data/lib/rack/runtime.rb +16 -3
- data/lib/rack/sendfile.rb +47 -29
- data/lib/rack/server.rb +223 -47
- data/lib/rack/session/abstract/id.rb +289 -30
- data/lib/rack/session/cookie.rb +133 -44
- data/lib/rack/session/memcache.rb +30 -56
- data/lib/rack/session/pool.rb +19 -43
- data/lib/rack/showexceptions.rb +53 -15
- data/lib/rack/showstatus.rb +14 -7
- data/lib/rack/static.rb +124 -12
- data/lib/rack/tempfile_reaper.rb +22 -0
- data/lib/rack/urlmap.rb +49 -15
- data/lib/rack/utils/okjson.rb +600 -0
- data/lib/rack/utils.rb +363 -361
- data/lib/rack.rb +17 -23
- data/rack.gemspec +11 -20
- data/test/builder/anything.rb +5 -0
- data/test/builder/comment.ru +4 -0
- data/test/builder/end.ru +5 -0
- data/test/builder/line.ru +1 -0
- data/test/builder/options.ru +2 -0
- data/test/cgi/assets/folder/test.js +1 -0
- data/test/cgi/assets/fonts/font.eot +1 -0
- data/test/cgi/assets/images/image.png +1 -0
- data/test/cgi/assets/index.html +1 -0
- data/test/cgi/assets/javascripts/app.js +1 -0
- data/test/cgi/assets/stylesheets/app.css +1 -0
- data/test/cgi/lighttpd.conf +26 -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+directory/test+file +1 -0
- data/test/cgi/test.fcgi +8 -0
- data/test/cgi/test.ru +5 -0
- data/test/gemloader.rb +10 -0
- data/test/multipart/bad_robots +259 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +6 -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_and_no_name +6 -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_null_byte +7 -0
- data/test/multipart/filename_with_percent_escaped_quotes +6 -0
- data/test/multipart/filename_with_unescaped_percentages +6 -0
- data/test/multipart/filename_with_unescaped_percentages2 +6 -0
- data/test/multipart/filename_with_unescaped_percentages3 +6 -0
- data/test/multipart/filename_with_unescaped_quotes +6 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/invalid_character +6 -0
- data/test/multipart/mixed_files +21 -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/multipart/three_files_three_fields +31 -0
- data/test/multipart/webkit +32 -0
- data/test/rackup/config.ru +31 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
- data/test/{spec_rack_auth_basic.rb → spec_auth_basic.rb} +23 -15
- data/test/{spec_rack_auth_digest.rb → spec_auth_digest.rb} +56 -29
- data/test/spec_body_proxy.rb +85 -0
- data/test/spec_builder.rb +223 -0
- data/test/{spec_rack_cascade.rb → spec_cascade.rb} +28 -15
- data/test/{spec_rack_cgi.rb → spec_cgi.rb} +44 -31
- data/test/spec_chunked.rb +101 -0
- data/test/spec_commonlogger.rb +93 -0
- data/test/spec_conditionalget.rb +102 -0
- data/test/{spec_rack_config.rb → spec_config.rb} +6 -8
- data/test/spec_content_length.rb +85 -0
- data/test/spec_content_type.rb +45 -0
- data/test/spec_deflater.rb +339 -0
- data/test/{spec_rack_directory.rb → spec_directory.rb} +37 -10
- data/test/spec_etag.rb +107 -0
- data/test/{spec_rack_fastcgi.rb → spec_fastcgi.rb} +47 -29
- data/test/spec_file.rb +221 -0
- data/test/spec_handler.rb +72 -0
- data/test/spec_head.rb +45 -0
- data/test/{spec_rack_lint.rb → spec_lint.rb} +82 -60
- data/test/spec_lobster.rb +58 -0
- data/test/spec_lock.rb +164 -0
- data/test/spec_logger.rb +23 -0
- data/test/spec_methodoverride.rb +95 -0
- data/test/spec_mime.rb +51 -0
- data/test/{spec_rack_mock.rb → spec_mock.rb} +92 -38
- data/test/{spec_rack_mongrel.rb → spec_mongrel.rb} +46 -53
- data/test/spec_multipart.rb +600 -0
- data/test/spec_nulllogger.rb +20 -0
- data/test/spec_recursive.rb +72 -0
- data/test/spec_request.rb +1227 -0
- data/test/spec_response.rb +407 -0
- data/test/spec_rewindable_input.rb +118 -0
- data/test/spec_runtime.rb +49 -0
- data/test/spec_sendfile.rb +130 -0
- data/test/spec_server.rb +167 -0
- data/test/spec_session_abstract_id.rb +53 -0
- data/test/spec_session_cookie.rb +410 -0
- data/test/{spec_rack_session_memcache.rb → spec_session_memcache.rb} +119 -71
- data/test/{spec_rack_session_pool.rb → spec_session_pool.rb} +106 -69
- data/test/spec_showexceptions.rb +85 -0
- data/test/spec_showstatus.rb +103 -0
- data/test/spec_static.rb +145 -0
- data/test/spec_tempfile_reaper.rb +63 -0
- data/test/{spec_rack_thin.rb → spec_thin.rb} +35 -35
- data/test/{spec_rack_urlmap.rb → spec_urlmap.rb} +40 -19
- data/test/spec_utils.rb +647 -0
- data/test/spec_version.rb +17 -0
- data/test/spec_webrick.rb +184 -0
- data/test/static/another/index.html +1 -0
- data/test/static/index.html +1 -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 +220 -239
- data/RDOX +0 -0
- data/README +0 -592
- data/lib/rack/adapter/camping.rb +0 -22
- data/test/spec_auth.rb +0 -57
- data/test/spec_rack_builder.rb +0 -84
- data/test/spec_rack_camping.rb +0 -55
- data/test/spec_rack_chunked.rb +0 -62
- data/test/spec_rack_commonlogger.rb +0 -61
- data/test/spec_rack_conditionalget.rb +0 -41
- data/test/spec_rack_content_length.rb +0 -43
- data/test/spec_rack_content_type.rb +0 -30
- data/test/spec_rack_deflater.rb +0 -127
- data/test/spec_rack_etag.rb +0 -17
- data/test/spec_rack_file.rb +0 -75
- data/test/spec_rack_handler.rb +0 -43
- data/test/spec_rack_head.rb +0 -30
- data/test/spec_rack_lobster.rb +0 -45
- data/test/spec_rack_lock.rb +0 -38
- data/test/spec_rack_logger.rb +0 -21
- data/test/spec_rack_methodoverride.rb +0 -60
- data/test/spec_rack_nulllogger.rb +0 -13
- data/test/spec_rack_recursive.rb +0 -77
- data/test/spec_rack_request.rb +0 -594
- data/test/spec_rack_response.rb +0 -221
- data/test/spec_rack_rewindable_input.rb +0 -118
- data/test/spec_rack_runtime.rb +0 -35
- data/test/spec_rack_sendfile.rb +0 -86
- data/test/spec_rack_session_cookie.rb +0 -92
- data/test/spec_rack_showexceptions.rb +0 -21
- data/test/spec_rack_showstatus.rb +0 -72
- data/test/spec_rack_static.rb +0 -37
- data/test/spec_rack_utils.rb +0 -557
- data/test/spec_rack_webrick.rb +0 -130
- data/test/spec_rackup.rb +0 -164
|
@@ -4,12 +4,163 @@
|
|
|
4
4
|
require 'time'
|
|
5
5
|
require 'rack/request'
|
|
6
6
|
require 'rack/response'
|
|
7
|
+
begin
|
|
8
|
+
require 'securerandom'
|
|
9
|
+
rescue LoadError
|
|
10
|
+
# We just won't get securerandom
|
|
11
|
+
end
|
|
7
12
|
|
|
8
13
|
module Rack
|
|
9
14
|
|
|
10
15
|
module Session
|
|
11
16
|
|
|
12
17
|
module Abstract
|
|
18
|
+
ENV_SESSION_KEY = 'rack.session'.freeze
|
|
19
|
+
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
|
|
20
|
+
|
|
21
|
+
# SessionHash is responsible to lazily load the session from store.
|
|
22
|
+
|
|
23
|
+
class SessionHash
|
|
24
|
+
include Enumerable
|
|
25
|
+
attr_writer :id
|
|
26
|
+
|
|
27
|
+
def self.find(env)
|
|
28
|
+
env[ENV_SESSION_KEY]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.set(env, session)
|
|
32
|
+
env[ENV_SESSION_KEY] = session
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.set_options(env, options)
|
|
36
|
+
env[ENV_SESSION_OPTIONS_KEY] = options.dup
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def initialize(store, env)
|
|
40
|
+
@store = store
|
|
41
|
+
@env = env
|
|
42
|
+
@loaded = false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def id
|
|
46
|
+
return @id if @loaded or instance_variable_defined?(:@id)
|
|
47
|
+
@id = @store.send(:extract_session_id, @env)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def options
|
|
51
|
+
@env[ENV_SESSION_OPTIONS_KEY]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def each(&block)
|
|
55
|
+
load_for_read!
|
|
56
|
+
@data.each(&block)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def [](key)
|
|
60
|
+
load_for_read!
|
|
61
|
+
@data[key.to_s]
|
|
62
|
+
end
|
|
63
|
+
alias :fetch :[]
|
|
64
|
+
|
|
65
|
+
def has_key?(key)
|
|
66
|
+
load_for_read!
|
|
67
|
+
@data.has_key?(key.to_s)
|
|
68
|
+
end
|
|
69
|
+
alias :key? :has_key?
|
|
70
|
+
alias :include? :has_key?
|
|
71
|
+
|
|
72
|
+
def []=(key, value)
|
|
73
|
+
load_for_write!
|
|
74
|
+
@data[key.to_s] = value
|
|
75
|
+
end
|
|
76
|
+
alias :store :[]=
|
|
77
|
+
|
|
78
|
+
def clear
|
|
79
|
+
load_for_write!
|
|
80
|
+
@data.clear
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def destroy
|
|
84
|
+
clear
|
|
85
|
+
@id = @store.send(:destroy_session, @env, id, options)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def to_hash
|
|
89
|
+
load_for_read!
|
|
90
|
+
@data.dup
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def update(hash)
|
|
94
|
+
load_for_write!
|
|
95
|
+
@data.update(stringify_keys(hash))
|
|
96
|
+
end
|
|
97
|
+
alias :merge! :update
|
|
98
|
+
|
|
99
|
+
def replace(hash)
|
|
100
|
+
load_for_write!
|
|
101
|
+
@data.replace(stringify_keys(hash))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def delete(key)
|
|
105
|
+
load_for_write!
|
|
106
|
+
@data.delete(key.to_s)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def inspect
|
|
110
|
+
if loaded?
|
|
111
|
+
@data.inspect
|
|
112
|
+
else
|
|
113
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def exists?
|
|
118
|
+
return @exists if instance_variable_defined?(:@exists)
|
|
119
|
+
@data = {}
|
|
120
|
+
@exists = @store.send(:session_exists?, @env)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def loaded?
|
|
124
|
+
@loaded
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def empty?
|
|
128
|
+
load_for_read!
|
|
129
|
+
@data.empty?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def keys
|
|
133
|
+
@data.keys
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def values
|
|
137
|
+
@data.values
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
def load_for_read!
|
|
143
|
+
load! if !loaded? && exists?
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def load_for_write!
|
|
147
|
+
load! unless loaded?
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def load!
|
|
151
|
+
@id, session = @store.send(:load_session, @env)
|
|
152
|
+
@data = stringify_keys(session)
|
|
153
|
+
@loaded = true
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def stringify_keys(other)
|
|
157
|
+
hash = {}
|
|
158
|
+
other.each do |key, value|
|
|
159
|
+
hash[key.to_s] = value
|
|
160
|
+
end
|
|
161
|
+
hash
|
|
162
|
+
end
|
|
163
|
+
end
|
|
13
164
|
|
|
14
165
|
# ID sets up a basic framework for implementing an id based sessioning
|
|
15
166
|
# service. Cookies sent to the client for maintaining sessions will only
|
|
@@ -21,7 +172,9 @@ module Rack
|
|
|
21
172
|
# 'rack.session'
|
|
22
173
|
# * :path, :domain, :expire_after, :secure, and :httponly set the related
|
|
23
174
|
# cookie options as by Rack::Response#add_cookie
|
|
24
|
-
# * :
|
|
175
|
+
# * :skip will not a set a cookie in the response nor update the session state
|
|
176
|
+
# * :defer will not set a cookie in the response but still update the session
|
|
177
|
+
# state if it is used with a backend
|
|
25
178
|
# * :renew (implementation dependent) will prompt the generation of a new
|
|
26
179
|
# session id, and migration of data to be referenced at the new id. If
|
|
27
180
|
# :defer is set, it will be overridden and the cookie will be set.
|
|
@@ -34,9 +187,13 @@ module Rack
|
|
|
34
187
|
# recommended to change its value.
|
|
35
188
|
#
|
|
36
189
|
# Is Rack::Utils::Context compatible.
|
|
190
|
+
#
|
|
191
|
+
# Not included by default; you must require 'rack/session/abstract/id'
|
|
192
|
+
# to use.
|
|
37
193
|
|
|
38
194
|
class ID
|
|
39
195
|
DEFAULT_OPTIONS = {
|
|
196
|
+
:key => 'rack.session',
|
|
40
197
|
:path => '/',
|
|
41
198
|
:domain => nil,
|
|
42
199
|
:expire_after => nil,
|
|
@@ -44,14 +201,19 @@ module Rack
|
|
|
44
201
|
:httponly => true,
|
|
45
202
|
:defer => false,
|
|
46
203
|
:renew => false,
|
|
47
|
-
:sidbits => 128
|
|
204
|
+
:sidbits => 128,
|
|
205
|
+
:cookie_only => true,
|
|
206
|
+
:secure_random => (::SecureRandom rescue false)
|
|
48
207
|
}
|
|
49
208
|
|
|
50
209
|
attr_reader :key, :default_options
|
|
210
|
+
|
|
51
211
|
def initialize(app, options={})
|
|
52
212
|
@app = app
|
|
53
|
-
@key = options[:key] || "rack.session"
|
|
54
213
|
@default_options = self.class::DEFAULT_OPTIONS.merge(options)
|
|
214
|
+
@key = @default_options.delete(:key)
|
|
215
|
+
@cookie_only = @default_options.delete(:cookie_only)
|
|
216
|
+
initialize_sid
|
|
55
217
|
end
|
|
56
218
|
|
|
57
219
|
def call(env)
|
|
@@ -59,40 +221,102 @@ module Rack
|
|
|
59
221
|
end
|
|
60
222
|
|
|
61
223
|
def context(env, app=@app)
|
|
62
|
-
|
|
224
|
+
prepare_session(env)
|
|
63
225
|
status, headers, body = app.call(env)
|
|
64
226
|
commit_session(env, status, headers, body)
|
|
65
227
|
end
|
|
66
228
|
|
|
67
229
|
private
|
|
68
230
|
|
|
231
|
+
def initialize_sid
|
|
232
|
+
@sidbits = @default_options[:sidbits]
|
|
233
|
+
@sid_secure = @default_options[:secure_random]
|
|
234
|
+
@sid_length = @sidbits / 4
|
|
235
|
+
end
|
|
236
|
+
|
|
69
237
|
# Generate a new session id using Ruby #rand. The size of the
|
|
70
238
|
# session id is controlled by the :sidbits option.
|
|
71
239
|
# Monkey patch this to use custom methods for session id generation.
|
|
72
240
|
|
|
73
|
-
def generate_sid
|
|
74
|
-
|
|
75
|
-
|
|
241
|
+
def generate_sid(secure = @sid_secure)
|
|
242
|
+
if secure
|
|
243
|
+
secure.hex(@sid_length)
|
|
244
|
+
else
|
|
245
|
+
"%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1)
|
|
246
|
+
end
|
|
247
|
+
rescue NotImplementedError
|
|
248
|
+
generate_sid(false)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Sets the lazy session at 'rack.session' and places options and session
|
|
252
|
+
# metadata into 'rack.session.options'.
|
|
253
|
+
|
|
254
|
+
def prepare_session(env)
|
|
255
|
+
session_was = env[ENV_SESSION_KEY]
|
|
256
|
+
env[ENV_SESSION_KEY] = session_class.new(self, env)
|
|
257
|
+
env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
|
|
258
|
+
env[ENV_SESSION_KEY].merge! session_was if session_was
|
|
76
259
|
end
|
|
77
260
|
|
|
78
261
|
# Extracts the session id from provided cookies and passes it and the
|
|
79
|
-
# environment to #get_session.
|
|
80
|
-
# 'rack.session', and places options and session metadata into
|
|
81
|
-
# 'rack.session.options'.
|
|
262
|
+
# environment to #get_session.
|
|
82
263
|
|
|
83
264
|
def load_session(env)
|
|
265
|
+
sid = current_session_id(env)
|
|
266
|
+
sid, session = get_session(env, sid)
|
|
267
|
+
[sid, session || {}]
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Extract session id from request object.
|
|
271
|
+
|
|
272
|
+
def extract_session_id(env)
|
|
84
273
|
request = Rack::Request.new(env)
|
|
85
|
-
|
|
274
|
+
sid = request.cookies[@key]
|
|
275
|
+
sid ||= request.params[@key] unless @cookie_only
|
|
276
|
+
sid
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Returns the current session id from the SessionHash.
|
|
280
|
+
|
|
281
|
+
def current_session_id(env)
|
|
282
|
+
env[ENV_SESSION_KEY].id
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Check if the session exists or not.
|
|
286
|
+
|
|
287
|
+
def session_exists?(env)
|
|
288
|
+
value = current_session_id(env)
|
|
289
|
+
value && !value.empty?
|
|
290
|
+
end
|
|
86
291
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
292
|
+
# Session should be committed if it was loaded, any of specific options like :renew, :drop
|
|
293
|
+
# or :expire_after was given and the security permissions match. Skips if skip is given.
|
|
294
|
+
|
|
295
|
+
def commit_session?(env, session, options)
|
|
296
|
+
if options[:skip]
|
|
297
|
+
false
|
|
298
|
+
else
|
|
299
|
+
has_session = loaded_session?(session) || forced_session_update?(session, options)
|
|
300
|
+
has_session && security_matches?(env, options)
|
|
92
301
|
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def loaded_session?(session)
|
|
305
|
+
!session.is_a?(session_class) || session.loaded?
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def forced_session_update?(session, options)
|
|
309
|
+
force_options?(options) && session && !session.empty?
|
|
310
|
+
end
|
|
93
311
|
|
|
94
|
-
|
|
95
|
-
|
|
312
|
+
def force_options?(options)
|
|
313
|
+
options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any?
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def security_matches?(env, options)
|
|
317
|
+
return true unless options[:secure]
|
|
318
|
+
request = Rack::Request.new(env)
|
|
319
|
+
request.ssl?
|
|
96
320
|
end
|
|
97
321
|
|
|
98
322
|
# Acquires the session from the environment and the session id from
|
|
@@ -101,25 +325,52 @@ module Rack
|
|
|
101
325
|
# response with the session's id.
|
|
102
326
|
|
|
103
327
|
def commit_session(env, status, headers, body)
|
|
104
|
-
session = env[
|
|
105
|
-
options =
|
|
106
|
-
|
|
328
|
+
session = env[ENV_SESSION_KEY]
|
|
329
|
+
options = session.options
|
|
330
|
+
|
|
331
|
+
if options[:drop] || options[:renew]
|
|
332
|
+
session_id = destroy_session(env, session.id || generate_sid, options)
|
|
333
|
+
return [status, headers, body] unless session_id
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
return [status, headers, body] unless commit_session?(env, session, options)
|
|
107
337
|
|
|
108
|
-
|
|
338
|
+
session.send(:load!) unless loaded_session?(session)
|
|
339
|
+
session_id ||= session.id
|
|
340
|
+
session_data = session.to_hash.delete_if { |k,v| v.nil? }
|
|
341
|
+
|
|
342
|
+
if not data = set_session(env, session_id, session_data, options)
|
|
109
343
|
env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
|
|
110
344
|
elsif options[:defer] and not options[:renew]
|
|
111
|
-
env["rack.errors"].puts("
|
|
345
|
+
env["rack.errors"].puts("Deferring cookie for #{session_id}") if $VERBOSE
|
|
112
346
|
else
|
|
113
347
|
cookie = Hash.new
|
|
114
|
-
cookie[:value] =
|
|
115
|
-
cookie[:expires] = Time.now + options[:expire_after]
|
|
116
|
-
|
|
348
|
+
cookie[:value] = data
|
|
349
|
+
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
|
350
|
+
cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
|
|
351
|
+
set_cookie(env, headers, cookie.merge!(options))
|
|
117
352
|
end
|
|
118
353
|
|
|
119
354
|
[status, headers, body]
|
|
120
355
|
end
|
|
121
356
|
|
|
122
|
-
#
|
|
357
|
+
# Sets the cookie back to the client with session id. We skip the cookie
|
|
358
|
+
# setting if the value didn't change (sid is the same) or expires was given.
|
|
359
|
+
|
|
360
|
+
def set_cookie(env, headers, cookie)
|
|
361
|
+
request = Rack::Request.new(env)
|
|
362
|
+
if request.cookies[@key] != cookie[:value] || cookie[:expires]
|
|
363
|
+
Utils.set_cookie_header!(headers, @key, cookie)
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Allow subclasses to prepare_session for different Session classes
|
|
368
|
+
|
|
369
|
+
def session_class
|
|
370
|
+
SessionHash
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# All thread safety and session retrieval procedures should occur here.
|
|
123
374
|
# Should return [session_id, session].
|
|
124
375
|
# If nil is provided as the session id, generation of a new valid id
|
|
125
376
|
# should occur within.
|
|
@@ -128,12 +379,20 @@ module Rack
|
|
|
128
379
|
raise '#get_session not implemented.'
|
|
129
380
|
end
|
|
130
381
|
|
|
131
|
-
# All thread safety and session storage
|
|
132
|
-
#
|
|
133
|
-
#
|
|
382
|
+
# All thread safety and session storage procedures should occur here.
|
|
383
|
+
# Must return the session id if the session was saved successfully, or
|
|
384
|
+
# false if the session could not be saved.
|
|
385
|
+
|
|
134
386
|
def set_session(env, sid, session, options)
|
|
135
387
|
raise '#set_session not implemented.'
|
|
136
388
|
end
|
|
389
|
+
|
|
390
|
+
# All thread safety and session destroy procedures should occur here.
|
|
391
|
+
# Should return a new session id or nil if options[:drop]
|
|
392
|
+
|
|
393
|
+
def destroy_session(env, sid, options)
|
|
394
|
+
raise '#destroy_session not implemented'
|
|
395
|
+
end
|
|
137
396
|
end
|
|
138
397
|
end
|
|
139
398
|
end
|
data/lib/rack/session/cookie.rb
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
require 'openssl'
|
|
2
|
+
require 'zlib'
|
|
2
3
|
require 'rack/request'
|
|
3
4
|
require 'rack/response'
|
|
5
|
+
require 'rack/session/abstract/id'
|
|
4
6
|
|
|
5
7
|
module Rack
|
|
6
8
|
|
|
7
9
|
module Session
|
|
8
10
|
|
|
9
11
|
# Rack::Session::Cookie provides simple cookie based session management.
|
|
10
|
-
#
|
|
11
|
-
# set to :key (default: rack.session).
|
|
12
|
+
# By default, the session is a Ruby Hash stored as base64 encoded marshalled
|
|
13
|
+
# data set to :key (default: rack.session). The object that encodes the
|
|
14
|
+
# session data is configurable and must respond to +encode+ and +decode+.
|
|
15
|
+
# Both methods must take a string and return a string.
|
|
16
|
+
#
|
|
12
17
|
# When the secret key is set, cookie data is checked for data integrity.
|
|
18
|
+
# The old secret key is also accepted and allows graceful secret rotation.
|
|
13
19
|
#
|
|
14
20
|
# Example:
|
|
15
21
|
#
|
|
@@ -17,17 +23,88 @@ module Rack
|
|
|
17
23
|
# :domain => 'foo.com',
|
|
18
24
|
# :path => '/',
|
|
19
25
|
# :expire_after => 2592000,
|
|
20
|
-
# :secret => 'change_me'
|
|
26
|
+
# :secret => 'change_me',
|
|
27
|
+
# :old_secret => 'also_change_me'
|
|
21
28
|
#
|
|
22
29
|
# All parameters are optional.
|
|
30
|
+
#
|
|
31
|
+
# Example of a cookie with no encoding:
|
|
32
|
+
#
|
|
33
|
+
# Rack::Session::Cookie.new(application, {
|
|
34
|
+
# :coder => Rack::Session::Cookie::Identity.new
|
|
35
|
+
# })
|
|
36
|
+
#
|
|
37
|
+
# Example of a cookie with custom encoding:
|
|
38
|
+
#
|
|
39
|
+
# Rack::Session::Cookie.new(application, {
|
|
40
|
+
# :coder => Class.new {
|
|
41
|
+
# def encode(str); str.reverse; end
|
|
42
|
+
# def decode(str); str.reverse; end
|
|
43
|
+
# }.new
|
|
44
|
+
# })
|
|
45
|
+
#
|
|
46
|
+
|
|
47
|
+
class Cookie < Abstract::ID
|
|
48
|
+
# Encode session cookies as Base64
|
|
49
|
+
class Base64
|
|
50
|
+
def encode(str)
|
|
51
|
+
[str].pack('m')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def decode(str)
|
|
55
|
+
str.unpack('m').first
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Encode session cookies as Marshaled Base64 data
|
|
59
|
+
class Marshal < Base64
|
|
60
|
+
def encode(str)
|
|
61
|
+
super(::Marshal.dump(str))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def decode(str)
|
|
65
|
+
return unless str
|
|
66
|
+
::Marshal.load(super(str)) rescue nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# N.B. Unlike other encoding methods, the contained objects must be a
|
|
71
|
+
# valid JSON composite type, either a Hash or an Array.
|
|
72
|
+
class JSON < Base64
|
|
73
|
+
def encode(obj)
|
|
74
|
+
super(::Rack::Utils::OkJson.encode(obj))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def decode(str)
|
|
78
|
+
return unless str
|
|
79
|
+
::Rack::Utils::OkJson.decode(super(str)) rescue nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
23
82
|
|
|
24
|
-
|
|
83
|
+
class ZipJSON < Base64
|
|
84
|
+
def encode(obj)
|
|
85
|
+
super(Zlib::Deflate.deflate(::Rack::Utils::OkJson.encode(obj)))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def decode(str)
|
|
89
|
+
return unless str
|
|
90
|
+
::Rack::Utils::OkJson.decode(Zlib::Inflate.inflate(super(str)))
|
|
91
|
+
rescue
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Use no encoding for session cookies
|
|
98
|
+
class Identity
|
|
99
|
+
def encode(str); str; end
|
|
100
|
+
def decode(str); str; end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
attr_reader :coder
|
|
25
104
|
|
|
26
105
|
def initialize(app, options={})
|
|
27
|
-
@
|
|
28
|
-
|
|
29
|
-
@secret = options[:secret]
|
|
30
|
-
warn <<-MSG unless @secret
|
|
106
|
+
@secrets = options.values_at(:secret, :old_secret).compact
|
|
107
|
+
warn <<-MSG unless @secrets.size >= 1
|
|
31
108
|
SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
|
|
32
109
|
This poses a security threat. It is strongly recommended that you
|
|
33
110
|
provide a secret to prevent exploits that may be possible from crafted
|
|
@@ -36,62 +113,74 @@ module Rack
|
|
|
36
113
|
|
|
37
114
|
Called from: #{caller[0]}.
|
|
38
115
|
MSG
|
|
39
|
-
@
|
|
40
|
-
|
|
41
|
-
:expire_after => nil}.merge(options)
|
|
116
|
+
@coder = options[:coder] ||= Base64::Marshal.new
|
|
117
|
+
super(app, options.merge!(:cookie_only => true))
|
|
42
118
|
end
|
|
43
119
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def get_session(env, sid)
|
|
123
|
+
data = unpacked_cookie_data(env)
|
|
124
|
+
data = persistent_session_id!(data)
|
|
125
|
+
[data["session_id"], data]
|
|
48
126
|
end
|
|
49
127
|
|
|
50
|
-
|
|
128
|
+
def extract_session_id(env)
|
|
129
|
+
unpacked_cookie_data(env)["session_id"]
|
|
130
|
+
end
|
|
51
131
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
132
|
+
def unpacked_cookie_data(env)
|
|
133
|
+
env["rack.session.unpacked_cookie_data"] ||= begin
|
|
134
|
+
request = Rack::Request.new(env)
|
|
135
|
+
session_data = request.cookies[@key]
|
|
55
136
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
137
|
+
if @secrets.size > 0 && session_data
|
|
138
|
+
digest, session_data = session_data.reverse.split("--", 2)
|
|
139
|
+
digest.reverse! if digest
|
|
140
|
+
session_data.reverse! if session_data
|
|
141
|
+
session_data = nil unless digest_match?(session_data, digest)
|
|
142
|
+
end
|
|
60
143
|
|
|
61
|
-
|
|
62
|
-
session_data = session_data.unpack("m*").first
|
|
63
|
-
session_data = Marshal.load(session_data)
|
|
64
|
-
env["rack.session"] = session_data
|
|
65
|
-
rescue
|
|
66
|
-
env["rack.session"] = Hash.new
|
|
144
|
+
coder.decode(session_data) || {}
|
|
67
145
|
end
|
|
146
|
+
end
|
|
68
147
|
|
|
69
|
-
|
|
148
|
+
def persistent_session_id!(data, sid=nil)
|
|
149
|
+
data ||= {}
|
|
150
|
+
data["session_id"] ||= sid || generate_sid
|
|
151
|
+
data
|
|
70
152
|
end
|
|
71
153
|
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
session_data =
|
|
154
|
+
def set_session(env, session_id, session, options)
|
|
155
|
+
session = session.merge("session_id" => session_id)
|
|
156
|
+
session_data = coder.encode(session)
|
|
75
157
|
|
|
76
|
-
if @
|
|
77
|
-
session_data
|
|
158
|
+
if @secrets.first
|
|
159
|
+
session_data << "--#{generate_hmac(session_data, @secrets.first)}"
|
|
78
160
|
end
|
|
79
161
|
|
|
80
162
|
if session_data.size > (4096 - @key.size)
|
|
81
|
-
env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.
|
|
163
|
+
env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
|
|
164
|
+
nil
|
|
82
165
|
else
|
|
83
|
-
|
|
84
|
-
cookie = Hash.new
|
|
85
|
-
cookie[:value] = session_data
|
|
86
|
-
cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
|
|
87
|
-
Utils.set_cookie_header!(headers, @key, cookie.merge(options))
|
|
166
|
+
session_data
|
|
88
167
|
end
|
|
168
|
+
end
|
|
89
169
|
|
|
90
|
-
|
|
170
|
+
def destroy_session(env, session_id, options)
|
|
171
|
+
# Nothing to do here, data is in the client
|
|
172
|
+
generate_sid unless options[:drop]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def digest_match?(data, digest)
|
|
176
|
+
return unless data && digest
|
|
177
|
+
@secrets.any? do |secret|
|
|
178
|
+
Rack::Utils.secure_compare(digest, generate_hmac(data, secret))
|
|
179
|
+
end
|
|
91
180
|
end
|
|
92
181
|
|
|
93
|
-
def generate_hmac(data)
|
|
94
|
-
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new,
|
|
182
|
+
def generate_hmac(data, secret)
|
|
183
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
|
|
95
184
|
end
|
|
96
185
|
|
|
97
186
|
end
|