rack 1.6.11 → 2.1.4
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 +4 -4
- data/CHANGELOG.md +77 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +89 -139
- data/Rakefile +27 -28
- data/SPEC +6 -7
- data/bin/rackup +1 -0
- data/contrib/rack_logo.svg +164 -111
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +4 -2
- data/example/protectedlobster.ru +3 -1
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +7 -1
- data/lib/rack/auth/basic.rb +4 -1
- data/lib/rack/auth/digest/md5.rb +9 -7
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +5 -4
- data/lib/rack/auth/digest/request.rb +3 -1
- data/lib/rack/body_proxy.rb +11 -9
- data/lib/rack/builder.rb +42 -18
- data/lib/rack/cascade.rb +6 -5
- data/lib/rack/chunked.rb +33 -10
- data/lib/rack/{commonlogger.rb → common_logger.rb} +11 -10
- data/lib/rack/{conditionalget.rb → conditional_get.rb} +3 -1
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +5 -3
- data/lib/rack/content_type.rb +3 -1
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +33 -53
- data/lib/rack/directory.rb +75 -60
- data/lib/rack/etag.rb +8 -5
- data/lib/rack/events.rb +156 -0
- data/lib/rack/file.rb +4 -149
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler/cgi.rb +18 -17
- data/lib/rack/handler/fastcgi.rb +17 -16
- data/lib/rack/handler/lsws.rb +14 -12
- data/lib/rack/handler/scgi.rb +22 -19
- data/lib/rack/handler/thin.rb +6 -1
- data/lib/rack/handler/webrick.rb +28 -28
- data/lib/rack/handler.rb +9 -26
- data/lib/rack/head.rb +17 -17
- data/lib/rack/lint.rb +54 -51
- data/lib/rack/lobster.rb +8 -6
- data/lib/rack/lock.rb +17 -10
- data/lib/rack/logger.rb +4 -2
- data/lib/rack/media_type.rb +43 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
- data/lib/rack/mime.rb +27 -6
- data/lib/rack/mock.rb +101 -60
- data/lib/rack/multipart/generator.rb +11 -12
- data/lib/rack/multipart/parser.rb +280 -161
- data/lib/rack/multipart/uploaded_file.rb +3 -2
- data/lib/rack/multipart.rb +39 -8
- data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
- data/lib/rack/query_parser.rb +218 -0
- data/lib/rack/recursive.rb +11 -9
- data/lib/rack/reloader.rb +10 -4
- data/lib/rack/request.rb +447 -305
- data/lib/rack/response.rb +196 -83
- data/lib/rack/rewindable_input.rb +5 -14
- data/lib/rack/runtime.rb +12 -18
- data/lib/rack/sendfile.rb +19 -14
- data/lib/rack/server.rb +118 -41
- data/lib/rack/session/abstract/id.rb +215 -94
- data/lib/rack/session/cookie.rb +45 -28
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +25 -16
- data/lib/rack/show_exceptions.rb +392 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +7 -5
- data/lib/rack/static.rb +41 -11
- data/lib/rack/tempfile_reaper.rb +4 -2
- data/lib/rack/urlmap.rb +25 -15
- data/lib/rack/utils.rb +186 -272
- data/lib/rack.rb +76 -24
- data/rack.gemspec +25 -14
- metadata +62 -182
- data/HISTORY.md +0 -375
- data/KNOWN-ISSUES +0 -44
- data/lib/rack/backports/uri/common_18.rb +0 -56
- data/lib/rack/backports/uri/common_192.rb +0 -52
- data/lib/rack/backports/uri/common_193.rb +0 -29
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -106
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/showexceptions.rb +0 -387
- data/lib/rack/utils/okjson.rb +0 -600
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -8
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -81
- data/test/spec_auth_digest.rb +0 -259
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -223
- data/test/spec_cascade.rb +0 -61
- data/test/spec_cgi.rb +0 -102
- data/test/spec_chunked.rb +0 -101
- data/test/spec_commonlogger.rb +0 -93
- data/test/spec_conditionalget.rb +0 -102
- data/test/spec_config.rb +0 -22
- data/test/spec_content_length.rb +0 -85
- data/test/spec_content_type.rb +0 -45
- data/test/spec_deflater.rb +0 -339
- data/test/spec_directory.rb +0 -88
- data/test/spec_etag.rb +0 -107
- data/test/spec_fastcgi.rb +0 -107
- data/test/spec_file.rb +0 -221
- data/test/spec_handler.rb +0 -72
- data/test/spec_head.rb +0 -45
- data/test/spec_lint.rb +0 -550
- data/test/spec_lobster.rb +0 -58
- data/test/spec_lock.rb +0 -164
- data/test/spec_logger.rb +0 -23
- data/test/spec_methodoverride.rb +0 -111
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -297
- data/test/spec_mongrel.rb +0 -182
- data/test/spec_multipart.rb +0 -600
- data/test/spec_nulllogger.rb +0 -20
- data/test/spec_recursive.rb +0 -72
- data/test/spec_request.rb +0 -1232
- data/test/spec_response.rb +0 -407
- data/test/spec_rewindable_input.rb +0 -118
- data/test/spec_runtime.rb +0 -49
- data/test/spec_sendfile.rb +0 -130
- data/test/spec_server.rb +0 -167
- data/test/spec_session_abstract_id.rb +0 -53
- data/test/spec_session_cookie.rb +0 -410
- data/test/spec_session_memcache.rb +0 -321
- data/test/spec_session_pool.rb +0 -209
- data/test/spec_showexceptions.rb +0 -98
- data/test/spec_showstatus.rb +0 -103
- data/test/spec_static.rb +0 -145
- data/test/spec_tempfile_reaper.rb +0 -63
- data/test/spec_thin.rb +0 -91
- data/test/spec_urlmap.rb +0 -236
- data/test/spec_utils.rb +0 -647
- data/test/spec_version.rb +0 -17
- data/test/spec_webrick.rb +0 -184
- data/test/static/another/index.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
|
@@ -1,54 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
|
|
2
4
|
# bugrep: Andreas Zehnder
|
|
3
5
|
|
|
6
|
+
require 'rack'
|
|
4
7
|
require 'time'
|
|
5
8
|
require 'rack/request'
|
|
6
9
|
require 'rack/response'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
rescue LoadError
|
|
10
|
-
# We just won't get securerandom
|
|
11
|
-
end
|
|
10
|
+
require 'securerandom'
|
|
11
|
+
require 'digest/sha2'
|
|
12
12
|
|
|
13
13
|
module Rack
|
|
14
14
|
|
|
15
15
|
module Session
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
class SessionId
|
|
18
|
+
ID_VERSION = 2
|
|
19
|
+
|
|
20
|
+
attr_reader :public_id
|
|
21
|
+
|
|
22
|
+
def initialize(public_id)
|
|
23
|
+
@public_id = public_id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def private_id
|
|
27
|
+
"#{ID_VERSION}::#{hash_sid(public_id)}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
alias :cookie_value :public_id
|
|
31
|
+
alias :to_s :public_id
|
|
20
32
|
|
|
33
|
+
def empty?; false; end
|
|
34
|
+
def inspect; public_id.inspect; end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def hash_sid(sid)
|
|
39
|
+
Digest::SHA256.hexdigest(sid)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
module Abstract
|
|
21
44
|
# SessionHash is responsible to lazily load the session from store.
|
|
22
45
|
|
|
23
46
|
class SessionHash
|
|
47
|
+
using Module.new {
|
|
48
|
+
refine Hash do
|
|
49
|
+
def transform_keys(&block)
|
|
50
|
+
hash = {}
|
|
51
|
+
each do |key, value|
|
|
52
|
+
hash[block.call(key)] = value
|
|
53
|
+
end
|
|
54
|
+
hash
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
} unless {}.respond_to?(:transform_keys)
|
|
58
|
+
|
|
24
59
|
include Enumerable
|
|
25
60
|
attr_writer :id
|
|
26
61
|
|
|
27
|
-
|
|
28
|
-
|
|
62
|
+
Unspecified = Object.new
|
|
63
|
+
|
|
64
|
+
def self.find(req)
|
|
65
|
+
req.get_header RACK_SESSION
|
|
29
66
|
end
|
|
30
67
|
|
|
31
|
-
def self.set(
|
|
32
|
-
|
|
68
|
+
def self.set(req, session)
|
|
69
|
+
req.set_header RACK_SESSION, session
|
|
33
70
|
end
|
|
34
71
|
|
|
35
|
-
def self.set_options(
|
|
36
|
-
|
|
72
|
+
def self.set_options(req, options)
|
|
73
|
+
req.set_header RACK_SESSION_OPTIONS, options.dup
|
|
37
74
|
end
|
|
38
75
|
|
|
39
|
-
def initialize(store,
|
|
76
|
+
def initialize(store, req)
|
|
40
77
|
@store = store
|
|
41
|
-
@
|
|
78
|
+
@req = req
|
|
42
79
|
@loaded = false
|
|
43
80
|
end
|
|
44
81
|
|
|
45
82
|
def id
|
|
46
83
|
return @id if @loaded or instance_variable_defined?(:@id)
|
|
47
|
-
@id = @store.send(:extract_session_id, @
|
|
84
|
+
@id = @store.send(:extract_session_id, @req)
|
|
48
85
|
end
|
|
49
86
|
|
|
50
87
|
def options
|
|
51
|
-
@
|
|
88
|
+
@req.session_options
|
|
52
89
|
end
|
|
53
90
|
|
|
54
91
|
def each(&block)
|
|
@@ -60,7 +97,15 @@ module Rack
|
|
|
60
97
|
load_for_read!
|
|
61
98
|
@data[key.to_s]
|
|
62
99
|
end
|
|
63
|
-
|
|
100
|
+
|
|
101
|
+
def fetch(key, default = Unspecified, &block)
|
|
102
|
+
load_for_read!
|
|
103
|
+
if default == Unspecified
|
|
104
|
+
@data.fetch(key.to_s, &block)
|
|
105
|
+
else
|
|
106
|
+
@data.fetch(key.to_s, default, &block)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
64
109
|
|
|
65
110
|
def has_key?(key)
|
|
66
111
|
load_for_read!
|
|
@@ -82,7 +127,7 @@ module Rack
|
|
|
82
127
|
|
|
83
128
|
def destroy
|
|
84
129
|
clear
|
|
85
|
-
@id = @store.send(:
|
|
130
|
+
@id = @store.send(:delete_session, @req, id, options)
|
|
86
131
|
end
|
|
87
132
|
|
|
88
133
|
def to_hash
|
|
@@ -117,7 +162,7 @@ module Rack
|
|
|
117
162
|
def exists?
|
|
118
163
|
return @exists if instance_variable_defined?(:@exists)
|
|
119
164
|
@data = {}
|
|
120
|
-
@exists = @store.send(:session_exists?, @
|
|
165
|
+
@exists = @store.send(:session_exists?, @req)
|
|
121
166
|
end
|
|
122
167
|
|
|
123
168
|
def loaded?
|
|
@@ -130,10 +175,12 @@ module Rack
|
|
|
130
175
|
end
|
|
131
176
|
|
|
132
177
|
def keys
|
|
178
|
+
load_for_read!
|
|
133
179
|
@data.keys
|
|
134
180
|
end
|
|
135
181
|
|
|
136
182
|
def values
|
|
183
|
+
load_for_read!
|
|
137
184
|
@data.values
|
|
138
185
|
end
|
|
139
186
|
|
|
@@ -148,30 +195,26 @@ module Rack
|
|
|
148
195
|
end
|
|
149
196
|
|
|
150
197
|
def load!
|
|
151
|
-
@id, session = @store.send(:load_session, @
|
|
198
|
+
@id, session = @store.send(:load_session, @req)
|
|
152
199
|
@data = stringify_keys(session)
|
|
153
200
|
@loaded = true
|
|
154
201
|
end
|
|
155
202
|
|
|
156
203
|
def stringify_keys(other)
|
|
157
|
-
|
|
158
|
-
other.each do |key, value|
|
|
159
|
-
hash[key.to_s] = value
|
|
160
|
-
end
|
|
161
|
-
hash
|
|
204
|
+
other.to_hash.transform_keys(&:to_s)
|
|
162
205
|
end
|
|
163
206
|
end
|
|
164
207
|
|
|
165
208
|
# ID sets up a basic framework for implementing an id based sessioning
|
|
166
209
|
# service. Cookies sent to the client for maintaining sessions will only
|
|
167
|
-
# contain an id reference. Only #
|
|
210
|
+
# contain an id reference. Only #find_session and #write_session are
|
|
168
211
|
# required to be overwritten.
|
|
169
212
|
#
|
|
170
213
|
# All parameters are optional.
|
|
171
214
|
# * :key determines the name of the cookie, by default it is
|
|
172
215
|
# 'rack.session'
|
|
173
216
|
# * :path, :domain, :expire_after, :secure, and :httponly set the related
|
|
174
|
-
# cookie options as by Rack::Response#
|
|
217
|
+
# cookie options as by Rack::Response#set_cookie
|
|
175
218
|
# * :skip will not a set a cookie in the response nor update the session state
|
|
176
219
|
# * :defer will not set a cookie in the response but still update the session
|
|
177
220
|
# state if it is used with a backend
|
|
@@ -182,33 +225,33 @@ module Rack
|
|
|
182
225
|
# id will be.
|
|
183
226
|
#
|
|
184
227
|
# These options can be set on a per request basis, at the location of
|
|
185
|
-
# env['rack.session.options']
|
|
186
|
-
# found within the options hash at the key :id. It is
|
|
187
|
-
# recommended to change its value.
|
|
228
|
+
# <tt>env['rack.session.options']</tt>. Additionally the id of the
|
|
229
|
+
# session can be found within the options hash at the key :id. It is
|
|
230
|
+
# highly not recommended to change its value.
|
|
188
231
|
#
|
|
189
232
|
# Is Rack::Utils::Context compatible.
|
|
190
233
|
#
|
|
191
234
|
# Not included by default; you must require 'rack/session/abstract/id'
|
|
192
235
|
# to use.
|
|
193
236
|
|
|
194
|
-
class
|
|
237
|
+
class Persisted
|
|
195
238
|
DEFAULT_OPTIONS = {
|
|
196
|
-
:
|
|
197
|
-
:
|
|
198
|
-
:
|
|
199
|
-
:
|
|
200
|
-
:
|
|
201
|
-
:
|
|
202
|
-
:
|
|
203
|
-
:
|
|
204
|
-
:
|
|
205
|
-
:
|
|
206
|
-
:
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
attr_reader :key, :default_options
|
|
210
|
-
|
|
211
|
-
def initialize(app, options={})
|
|
239
|
+
key: RACK_SESSION,
|
|
240
|
+
path: '/',
|
|
241
|
+
domain: nil,
|
|
242
|
+
expire_after: nil,
|
|
243
|
+
secure: false,
|
|
244
|
+
httponly: true,
|
|
245
|
+
defer: false,
|
|
246
|
+
renew: false,
|
|
247
|
+
sidbits: 128,
|
|
248
|
+
cookie_only: true,
|
|
249
|
+
secure_random: ::SecureRandom
|
|
250
|
+
}.freeze
|
|
251
|
+
|
|
252
|
+
attr_reader :key, :default_options, :sid_secure
|
|
253
|
+
|
|
254
|
+
def initialize(app, options = {})
|
|
212
255
|
@app = app
|
|
213
256
|
@default_options = self.class::DEFAULT_OPTIONS.merge(options)
|
|
214
257
|
@key = @default_options.delete(:key)
|
|
@@ -220,14 +263,21 @@ module Rack
|
|
|
220
263
|
context(env)
|
|
221
264
|
end
|
|
222
265
|
|
|
223
|
-
def context(env, app
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
266
|
+
def context(env, app = @app)
|
|
267
|
+
req = make_request env
|
|
268
|
+
prepare_session(req)
|
|
269
|
+
status, headers, body = app.call(req.env)
|
|
270
|
+
res = Rack::Response::Raw.new status, headers
|
|
271
|
+
commit_session(req, res)
|
|
272
|
+
[status, headers, body]
|
|
227
273
|
end
|
|
228
274
|
|
|
229
275
|
private
|
|
230
276
|
|
|
277
|
+
def make_request(env)
|
|
278
|
+
Rack::Request.new env
|
|
279
|
+
end
|
|
280
|
+
|
|
231
281
|
def initialize_sid
|
|
232
282
|
@sidbits = @default_options[:sidbits]
|
|
233
283
|
@sid_secure = @default_options[:secure_random]
|
|
@@ -251,26 +301,26 @@ module Rack
|
|
|
251
301
|
# Sets the lazy session at 'rack.session' and places options and session
|
|
252
302
|
# metadata into 'rack.session.options'.
|
|
253
303
|
|
|
254
|
-
def prepare_session(
|
|
255
|
-
session_was
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
304
|
+
def prepare_session(req)
|
|
305
|
+
session_was = req.get_header RACK_SESSION
|
|
306
|
+
session = session_class.new(self, req)
|
|
307
|
+
req.set_header RACK_SESSION, session
|
|
308
|
+
req.set_header RACK_SESSION_OPTIONS, @default_options.dup
|
|
309
|
+
session.merge! session_was if session_was
|
|
259
310
|
end
|
|
260
311
|
|
|
261
312
|
# Extracts the session id from provided cookies and passes it and the
|
|
262
|
-
# environment to #
|
|
313
|
+
# environment to #find_session.
|
|
263
314
|
|
|
264
|
-
def load_session(
|
|
265
|
-
sid = current_session_id(
|
|
266
|
-
sid, session =
|
|
315
|
+
def load_session(req)
|
|
316
|
+
sid = current_session_id(req)
|
|
317
|
+
sid, session = find_session(req, sid)
|
|
267
318
|
[sid, session || {}]
|
|
268
319
|
end
|
|
269
320
|
|
|
270
321
|
# Extract session id from request object.
|
|
271
322
|
|
|
272
|
-
def extract_session_id(
|
|
273
|
-
request = Rack::Request.new(env)
|
|
323
|
+
def extract_session_id(request)
|
|
274
324
|
sid = request.cookies[@key]
|
|
275
325
|
sid ||= request.params[@key] unless @cookie_only
|
|
276
326
|
sid
|
|
@@ -278,26 +328,26 @@ module Rack
|
|
|
278
328
|
|
|
279
329
|
# Returns the current session id from the SessionHash.
|
|
280
330
|
|
|
281
|
-
def current_session_id(
|
|
282
|
-
|
|
331
|
+
def current_session_id(req)
|
|
332
|
+
req.get_header(RACK_SESSION).id
|
|
283
333
|
end
|
|
284
334
|
|
|
285
335
|
# Check if the session exists or not.
|
|
286
336
|
|
|
287
|
-
def session_exists?(
|
|
288
|
-
value = current_session_id(
|
|
337
|
+
def session_exists?(req)
|
|
338
|
+
value = current_session_id(req)
|
|
289
339
|
value && !value.empty?
|
|
290
340
|
end
|
|
291
341
|
|
|
292
342
|
# Session should be committed if it was loaded, any of specific options like :renew, :drop
|
|
293
343
|
# or :expire_after was given and the security permissions match. Skips if skip is given.
|
|
294
344
|
|
|
295
|
-
def commit_session?(
|
|
345
|
+
def commit_session?(req, session, options)
|
|
296
346
|
if options[:skip]
|
|
297
347
|
false
|
|
298
348
|
else
|
|
299
349
|
has_session = loaded_session?(session) || forced_session_update?(session, options)
|
|
300
|
-
has_session && security_matches?(
|
|
350
|
+
has_session && security_matches?(req, options)
|
|
301
351
|
end
|
|
302
352
|
end
|
|
303
353
|
|
|
@@ -313,54 +363,56 @@ module Rack
|
|
|
313
363
|
options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any?
|
|
314
364
|
end
|
|
315
365
|
|
|
316
|
-
def security_matches?(
|
|
366
|
+
def security_matches?(request, options)
|
|
317
367
|
return true unless options[:secure]
|
|
318
|
-
request = Rack::Request.new(env)
|
|
319
368
|
request.ssl?
|
|
320
369
|
end
|
|
321
370
|
|
|
322
371
|
# Acquires the session from the environment and the session id from
|
|
323
|
-
# the session options and passes them to #
|
|
372
|
+
# the session options and passes them to #write_session. If successful
|
|
324
373
|
# and the :defer option is not true, a cookie will be added to the
|
|
325
374
|
# response with the session's id.
|
|
326
375
|
|
|
327
|
-
def commit_session(
|
|
328
|
-
session =
|
|
376
|
+
def commit_session(req, res)
|
|
377
|
+
session = req.get_header RACK_SESSION
|
|
329
378
|
options = session.options
|
|
330
379
|
|
|
331
380
|
if options[:drop] || options[:renew]
|
|
332
|
-
session_id =
|
|
333
|
-
return
|
|
381
|
+
session_id = delete_session(req, session.id || generate_sid, options)
|
|
382
|
+
return unless session_id
|
|
334
383
|
end
|
|
335
384
|
|
|
336
|
-
return
|
|
385
|
+
return unless commit_session?(req, session, options)
|
|
337
386
|
|
|
338
387
|
session.send(:load!) unless loaded_session?(session)
|
|
339
388
|
session_id ||= session.id
|
|
340
|
-
session_data = session.to_hash.delete_if { |k,v| v.nil? }
|
|
389
|
+
session_data = session.to_hash.delete_if { |k, v| v.nil? }
|
|
341
390
|
|
|
342
|
-
if not data =
|
|
343
|
-
|
|
391
|
+
if not data = write_session(req, session_id, session_data, options)
|
|
392
|
+
req.get_header(RACK_ERRORS).puts("Warning! #{self.class.name} failed to save session. Content dropped.")
|
|
344
393
|
elsif options[:defer] and not options[:renew]
|
|
345
|
-
|
|
394
|
+
req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
|
|
346
395
|
else
|
|
347
396
|
cookie = Hash.new
|
|
348
|
-
cookie[:value] = data
|
|
397
|
+
cookie[:value] = cookie_value(data)
|
|
349
398
|
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
|
350
399
|
cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
|
|
351
|
-
set_cookie(
|
|
400
|
+
set_cookie(req, res, cookie.merge!(options))
|
|
352
401
|
end
|
|
402
|
+
end
|
|
403
|
+
public :commit_session
|
|
353
404
|
|
|
354
|
-
|
|
405
|
+
def cookie_value(data)
|
|
406
|
+
data
|
|
355
407
|
end
|
|
356
408
|
|
|
357
409
|
# Sets the cookie back to the client with session id. We skip the cookie
|
|
358
410
|
# setting if the value didn't change (sid is the same) or expires was given.
|
|
359
411
|
|
|
360
|
-
def set_cookie(
|
|
361
|
-
request = Rack::Request.new(env)
|
|
412
|
+
def set_cookie(request, res, cookie)
|
|
362
413
|
if request.cookies[@key] != cookie[:value] || cookie[:expires]
|
|
363
|
-
|
|
414
|
+
res.set_cookie_header =
|
|
415
|
+
Utils.add_cookie_to_header(res.set_cookie_header, @key, cookie)
|
|
364
416
|
end
|
|
365
417
|
end
|
|
366
418
|
|
|
@@ -375,23 +427,92 @@ module Rack
|
|
|
375
427
|
# If nil is provided as the session id, generation of a new valid id
|
|
376
428
|
# should occur within.
|
|
377
429
|
|
|
378
|
-
def
|
|
379
|
-
raise '#
|
|
430
|
+
def find_session(env, sid)
|
|
431
|
+
raise '#find_session not implemented.'
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# All thread safety and session storage procedures should occur here.
|
|
435
|
+
# Must return the session id if the session was saved successfully, or
|
|
436
|
+
# false if the session could not be saved.
|
|
437
|
+
|
|
438
|
+
def write_session(req, sid, session, options)
|
|
439
|
+
raise '#write_session not implemented.'
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# All thread safety and session destroy procedures should occur here.
|
|
443
|
+
# Should return a new session id or nil if options[:drop]
|
|
444
|
+
|
|
445
|
+
def delete_session(req, sid, options)
|
|
446
|
+
raise '#delete_session not implemented'
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
class PersistedSecure < Persisted
|
|
451
|
+
class SecureSessionHash < SessionHash
|
|
452
|
+
def [](key)
|
|
453
|
+
if key == "session_id"
|
|
454
|
+
load_for_read!
|
|
455
|
+
id.public_id if id
|
|
456
|
+
else
|
|
457
|
+
super
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def generate_sid(*)
|
|
463
|
+
public_id = super
|
|
464
|
+
|
|
465
|
+
SessionId.new(public_id)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def extract_session_id(*)
|
|
469
|
+
public_id = super
|
|
470
|
+
public_id && SessionId.new(public_id)
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
private
|
|
474
|
+
|
|
475
|
+
def session_class
|
|
476
|
+
SecureSessionHash
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def cookie_value(data)
|
|
480
|
+
data.cookie_value
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
class ID < Persisted
|
|
485
|
+
def self.inherited(klass)
|
|
486
|
+
k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID }
|
|
487
|
+
unless k.instance_variable_defined?(:"@_rack_warned")
|
|
488
|
+
warn "#{klass} is inheriting from #{ID}. Inheriting from #{ID} is deprecated, please inherit from #{Persisted} instead" if $VERBOSE
|
|
489
|
+
k.instance_variable_set(:"@_rack_warned", true)
|
|
490
|
+
end
|
|
491
|
+
super
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# All thread safety and session retrieval procedures should occur here.
|
|
495
|
+
# Should return [session_id, session].
|
|
496
|
+
# If nil is provided as the session id, generation of a new valid id
|
|
497
|
+
# should occur within.
|
|
498
|
+
|
|
499
|
+
def find_session(req, sid)
|
|
500
|
+
get_session req.env, sid
|
|
380
501
|
end
|
|
381
502
|
|
|
382
503
|
# All thread safety and session storage procedures should occur here.
|
|
383
504
|
# Must return the session id if the session was saved successfully, or
|
|
384
505
|
# false if the session could not be saved.
|
|
385
506
|
|
|
386
|
-
def
|
|
387
|
-
|
|
507
|
+
def write_session(req, sid, session, options)
|
|
508
|
+
set_session req.env, sid, session, options
|
|
388
509
|
end
|
|
389
510
|
|
|
390
511
|
# All thread safety and session destroy procedures should occur here.
|
|
391
512
|
# Should return a new session id or nil if options[:drop]
|
|
392
513
|
|
|
393
|
-
def
|
|
394
|
-
|
|
514
|
+
def delete_session(req, sid, options)
|
|
515
|
+
destroy_session req.env, sid, options
|
|
395
516
|
end
|
|
396
517
|
end
|
|
397
518
|
end
|
data/lib/rack/session/cookie.rb
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'openssl'
|
|
2
4
|
require 'zlib'
|
|
3
5
|
require 'rack/request'
|
|
4
6
|
require 'rack/response'
|
|
5
7
|
require 'rack/session/abstract/id'
|
|
8
|
+
require 'json'
|
|
9
|
+
require 'base64'
|
|
6
10
|
|
|
7
11
|
module Rack
|
|
8
12
|
|
|
@@ -44,15 +48,15 @@ module Rack
|
|
|
44
48
|
# })
|
|
45
49
|
#
|
|
46
50
|
|
|
47
|
-
class Cookie < Abstract::
|
|
51
|
+
class Cookie < Abstract::PersistedSecure
|
|
48
52
|
# Encode session cookies as Base64
|
|
49
53
|
class Base64
|
|
50
54
|
def encode(str)
|
|
51
|
-
|
|
55
|
+
::Base64.strict_encode64(str)
|
|
52
56
|
end
|
|
53
57
|
|
|
54
58
|
def decode(str)
|
|
55
|
-
|
|
59
|
+
::Base64.decode64(str)
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
# Encode session cookies as Marshaled Base64 data
|
|
@@ -71,23 +75,23 @@ module Rack
|
|
|
71
75
|
# valid JSON composite type, either a Hash or an Array.
|
|
72
76
|
class JSON < Base64
|
|
73
77
|
def encode(obj)
|
|
74
|
-
super(::
|
|
78
|
+
super(::JSON.dump(obj))
|
|
75
79
|
end
|
|
76
80
|
|
|
77
81
|
def decode(str)
|
|
78
82
|
return unless str
|
|
79
|
-
::
|
|
83
|
+
::JSON.parse(super(str)) rescue nil
|
|
80
84
|
end
|
|
81
85
|
end
|
|
82
86
|
|
|
83
87
|
class ZipJSON < Base64
|
|
84
88
|
def encode(obj)
|
|
85
|
-
super(Zlib::Deflate.deflate(::
|
|
89
|
+
super(Zlib::Deflate.deflate(::JSON.dump(obj)))
|
|
86
90
|
end
|
|
87
91
|
|
|
88
92
|
def decode(str)
|
|
89
93
|
return unless str
|
|
90
|
-
::
|
|
94
|
+
::JSON.parse(Zlib::Inflate.inflate(super(str)))
|
|
91
95
|
rescue
|
|
92
96
|
nil
|
|
93
97
|
end
|
|
@@ -102,9 +106,11 @@ module Rack
|
|
|
102
106
|
|
|
103
107
|
attr_reader :coder
|
|
104
108
|
|
|
105
|
-
def initialize(app, options={})
|
|
109
|
+
def initialize(app, options = {})
|
|
106
110
|
@secrets = options.values_at(:secret, :old_secret).compact
|
|
107
|
-
|
|
111
|
+
@hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1)
|
|
112
|
+
|
|
113
|
+
warn <<-MSG unless secure?(options)
|
|
108
114
|
SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
|
|
109
115
|
This poses a security threat. It is strongly recommended that you
|
|
110
116
|
provide a secret to prevent exploits that may be possible from crafted
|
|
@@ -113,45 +119,51 @@ module Rack
|
|
|
113
119
|
|
|
114
120
|
Called from: #{caller[0]}.
|
|
115
121
|
MSG
|
|
116
|
-
@coder
|
|
117
|
-
super(app, options.merge!(:
|
|
122
|
+
@coder = options[:coder] ||= Base64::Marshal.new
|
|
123
|
+
super(app, options.merge!(cookie_only: true))
|
|
118
124
|
end
|
|
119
125
|
|
|
120
126
|
private
|
|
121
127
|
|
|
122
|
-
def
|
|
123
|
-
data = unpacked_cookie_data(
|
|
128
|
+
def find_session(req, sid)
|
|
129
|
+
data = unpacked_cookie_data(req)
|
|
124
130
|
data = persistent_session_id!(data)
|
|
125
131
|
[data["session_id"], data]
|
|
126
132
|
end
|
|
127
133
|
|
|
128
|
-
def extract_session_id(
|
|
129
|
-
unpacked_cookie_data(
|
|
134
|
+
def extract_session_id(request)
|
|
135
|
+
unpacked_cookie_data(request)["session_id"]
|
|
130
136
|
end
|
|
131
137
|
|
|
132
|
-
def unpacked_cookie_data(
|
|
133
|
-
|
|
134
|
-
request = Rack::Request.new(env)
|
|
138
|
+
def unpacked_cookie_data(request)
|
|
139
|
+
request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k|
|
|
135
140
|
session_data = request.cookies[@key]
|
|
136
141
|
|
|
137
142
|
if @secrets.size > 0 && session_data
|
|
138
|
-
|
|
139
|
-
digest.reverse! if digest
|
|
140
|
-
session_data.reverse! if session_data
|
|
143
|
+
session_data, _, digest = session_data.rpartition('--')
|
|
141
144
|
session_data = nil unless digest_match?(session_data, digest)
|
|
142
145
|
end
|
|
143
146
|
|
|
144
|
-
coder.decode(session_data) || {}
|
|
147
|
+
request.set_header(k, coder.decode(session_data) || {})
|
|
145
148
|
end
|
|
146
149
|
end
|
|
147
150
|
|
|
148
|
-
def persistent_session_id!(data, sid=nil)
|
|
151
|
+
def persistent_session_id!(data, sid = nil)
|
|
149
152
|
data ||= {}
|
|
150
153
|
data["session_id"] ||= sid || generate_sid
|
|
151
154
|
data
|
|
152
155
|
end
|
|
153
156
|
|
|
154
|
-
|
|
157
|
+
class SessionId < DelegateClass(Session::SessionId)
|
|
158
|
+
attr_reader :cookie_value
|
|
159
|
+
|
|
160
|
+
def initialize(session_id, cookie_value)
|
|
161
|
+
super(session_id)
|
|
162
|
+
@cookie_value = cookie_value
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def write_session(req, session_id, session, options)
|
|
155
167
|
session = session.merge("session_id" => session_id)
|
|
156
168
|
session_data = coder.encode(session)
|
|
157
169
|
|
|
@@ -160,14 +172,14 @@ module Rack
|
|
|
160
172
|
end
|
|
161
173
|
|
|
162
174
|
if session_data.size > (4096 - @key.size)
|
|
163
|
-
|
|
175
|
+
req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
|
|
164
176
|
nil
|
|
165
177
|
else
|
|
166
|
-
session_data
|
|
178
|
+
SessionId.new(session_id, session_data)
|
|
167
179
|
end
|
|
168
180
|
end
|
|
169
181
|
|
|
170
|
-
def
|
|
182
|
+
def delete_session(req, session_id, options)
|
|
171
183
|
# Nothing to do here, data is in the client
|
|
172
184
|
generate_sid unless options[:drop]
|
|
173
185
|
end
|
|
@@ -180,7 +192,12 @@ module Rack
|
|
|
180
192
|
end
|
|
181
193
|
|
|
182
194
|
def generate_hmac(data, secret)
|
|
183
|
-
OpenSSL::HMAC.hexdigest(
|
|
195
|
+
OpenSSL::HMAC.hexdigest(@hmac.new, secret, data)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def secure?(options)
|
|
199
|
+
@secrets.size >= 1 ||
|
|
200
|
+
(options[:coder] && options[:let_coder_handle_secure_encoding])
|
|
184
201
|
end
|
|
185
202
|
|
|
186
203
|
end
|