rack 2.0.6 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/{HISTORY.md → CHANGELOG.md} +220 -155
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +77 -117
- data/Rakefile +25 -18
- data/SPEC +3 -4
- data/bin/rackup +1 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +3 -1
- data/example/protectedlobster.ru +2 -0
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +2 -0
- 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 +4 -2
- data/lib/rack/auth/digest/request.rb +2 -0
- data/lib/rack/body_proxy.rb +3 -6
- data/lib/rack/builder.rb +38 -15
- data/lib/rack/cascade.rb +6 -5
- data/lib/rack/chunked.rb +29 -6
- data/lib/rack/common_logger.rb +9 -8
- data/lib/rack/conditional_get.rb +3 -1
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +3 -1
- data/lib/rack/content_type.rb +3 -1
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +28 -17
- data/lib/rack/directory.rb +17 -14
- data/lib/rack/etag.rb +3 -1
- data/lib/rack/events.rb +5 -3
- data/lib/rack/file.rb +5 -173
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler/cgi.rb +3 -1
- data/lib/rack/handler/fastcgi.rb +4 -2
- data/lib/rack/handler/lsws.rb +3 -1
- data/lib/rack/handler/scgi.rb +9 -6
- data/lib/rack/handler/thin.rb +3 -1
- data/lib/rack/handler/webrick.rb +4 -2
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/head.rb +2 -0
- data/lib/rack/lint.rb +14 -11
- data/lib/rack/lobster.rb +7 -5
- data/lib/rack/lock.rb +2 -0
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +10 -5
- data/lib/rack/method_override.rb +4 -2
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +74 -15
- data/lib/rack/multipart/generator.rb +6 -7
- data/lib/rack/multipart/parser.rb +55 -52
- data/lib/rack/multipart/uploaded_file.rb +2 -0
- data/lib/rack/multipart.rb +5 -3
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +51 -25
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +10 -4
- data/lib/rack/request.rb +80 -27
- data/lib/rack/response.rb +71 -31
- data/lib/rack/rewindable_input.rb +4 -2
- data/lib/rack/runtime.rb +4 -2
- data/lib/rack/sendfile.rb +15 -8
- data/lib/rack/server.rb +88 -16
- data/lib/rack/session/abstract/id.rb +104 -21
- data/lib/rack/session/cookie.rb +21 -11
- data/lib/rack/session/memcache.rb +4 -87
- data/lib/rack/session/pool.rb +17 -8
- data/lib/rack/show_exceptions.rb +15 -9
- data/lib/rack/show_status.rb +4 -2
- data/lib/rack/static.rb +15 -10
- data/lib/rack/tempfile_reaper.rb +2 -0
- data/lib/rack/urlmap.rb +11 -2
- data/lib/rack/utils.rb +55 -70
- data/lib/rack.rb +63 -60
- data/rack.gemspec +17 -7
- metadata +30 -171
- data/test/builder/an_underscore_app.rb +0 -5
- 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 -9
- data/test/cgi/test.gz +0 -0
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/helper.rb +0 -34
- 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_encoded_words +0 -7
- 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_single_quote +0 -7
- 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/quoted +0 -15
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/unity3d_wwwform +0 -11
- 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 -89
- data/test/spec_auth_digest.rb +0 -260
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -233
- data/test/spec_cascade.rb +0 -63
- data/test/spec_cgi.rb +0 -84
- data/test/spec_chunked.rb +0 -103
- data/test/spec_common_logger.rb +0 -95
- data/test/spec_conditional_get.rb +0 -103
- data/test/spec_config.rb +0 -23
- data/test/spec_content_length.rb +0 -86
- data/test/spec_content_type.rb +0 -46
- data/test/spec_deflater.rb +0 -375
- data/test/spec_directory.rb +0 -148
- data/test/spec_etag.rb +0 -108
- data/test/spec_events.rb +0 -133
- data/test/spec_fastcgi.rb +0 -85
- data/test/spec_file.rb +0 -264
- data/test/spec_handler.rb +0 -57
- data/test/spec_head.rb +0 -46
- data/test/spec_lint.rb +0 -515
- data/test/spec_lobster.rb +0 -59
- data/test/spec_lock.rb +0 -204
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -110
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -359
- data/test/spec_multipart.rb +0 -722
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1398
- data/test/spec_response.rb +0 -510
- data/test/spec_rewindable_input.rb +0 -128
- data/test/spec_runtime.rb +0 -50
- data/test/spec_sendfile.rb +0 -125
- data/test/spec_server.rb +0 -193
- data/test/spec_session_abstract_id.rb +0 -31
- data/test/spec_session_abstract_session_hash.rb +0 -45
- data/test/spec_session_cookie.rb +0 -442
- data/test/spec_session_memcache.rb +0 -320
- data/test/spec_session_pool.rb +0 -210
- data/test/spec_show_exceptions.rb +0 -93
- data/test/spec_show_status.rb +0 -104
- data/test/spec_static.rb +0 -184
- data/test/spec_tempfile_reaper.rb +0 -64
- data/test/spec_thin.rb +0 -96
- data/test/spec_urlmap.rb +0 -237
- data/test/spec_utils.rb +0 -742
- data/test/spec_version.rb +0 -11
- data/test/spec_webrick.rb +0 -206
- data/test/static/another/index.html +0 -1
- data/test/static/foo.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
data/lib/rack/sendfile.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack/files'
|
2
4
|
require 'rack/body_proxy'
|
3
5
|
|
4
6
|
module Rack
|
@@ -14,7 +16,7 @@ module Rack
|
|
14
16
|
#
|
15
17
|
# In order to take advantage of this middleware, the response body must
|
16
18
|
# respond to +to_path+ and the request must include an X-Sendfile-Type
|
17
|
-
# header. Rack::
|
19
|
+
# header. Rack::Files and other components implement +to_path+ so there's
|
18
20
|
# rarely anything you need to do in your application. The X-Sendfile-Type
|
19
21
|
# header is typically set in your web servers configuration. The following
|
20
22
|
# sections attempt to document
|
@@ -53,7 +55,7 @@ module Rack
|
|
53
55
|
# that it maps to. The middleware performs a simple substitution on the
|
54
56
|
# resulting path.
|
55
57
|
#
|
56
|
-
# See Also:
|
58
|
+
# See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
|
57
59
|
#
|
58
60
|
# === lighttpd
|
59
61
|
#
|
@@ -99,7 +101,7 @@ module Rack
|
|
99
101
|
# will be matched with case indifference.
|
100
102
|
|
101
103
|
class Sendfile
|
102
|
-
def initialize(app, variation=nil, mappings=[])
|
104
|
+
def initialize(app, variation = nil, mappings = [])
|
103
105
|
@app = app
|
104
106
|
@variation = variation
|
105
107
|
@mappings = mappings.map do |internal, external|
|
@@ -115,7 +117,8 @@ module Rack
|
|
115
117
|
path = ::File.expand_path(body.to_path)
|
116
118
|
if url = map_accel_path(env, path)
|
117
119
|
headers[CONTENT_LENGTH] = '0'
|
118
|
-
|
120
|
+
# '?' must be percent-encoded because it is not query string but a part of path
|
121
|
+
headers[type] = ::Rack::Utils.escape_path(url).gsub('?', '%3F')
|
119
122
|
obody = body
|
120
123
|
body = Rack::BodyProxy.new([]) do
|
121
124
|
obody.close if obody.respond_to?(:close)
|
@@ -147,11 +150,15 @@ module Rack
|
|
147
150
|
end
|
148
151
|
|
149
152
|
def map_accel_path(env, path)
|
150
|
-
if mapping = @mappings.find { |internal,_| internal =~ path }
|
153
|
+
if mapping = @mappings.find { |internal, _| internal =~ path }
|
151
154
|
path.sub(*mapping)
|
152
155
|
elsif mapping = env['HTTP_X_ACCEL_MAPPING']
|
153
|
-
|
154
|
-
|
156
|
+
mapping.split(',').map(&:strip).each do |m|
|
157
|
+
internal, external = m.split('=', 2).map(&:strip)
|
158
|
+
new_path = path.sub(/^#{internal}/i, external)
|
159
|
+
return new_path unless path == new_path
|
160
|
+
end
|
161
|
+
path
|
155
162
|
end
|
156
163
|
end
|
157
164
|
end
|
data/lib/rack/server.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'optparse'
|
2
4
|
require 'fileutils'
|
3
5
|
|
6
|
+
require_relative 'core_ext/regexp'
|
4
7
|
|
5
8
|
module Rack
|
6
9
|
|
7
10
|
class Server
|
11
|
+
using ::Rack::RegexpExtensions
|
8
12
|
|
9
13
|
class Options
|
10
14
|
def parse!(args)
|
@@ -21,10 +25,6 @@ module Rack
|
|
21
25
|
lineno += 1
|
22
26
|
}
|
23
27
|
|
24
|
-
opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
|
25
|
-
options[:builder] = line
|
26
|
-
}
|
27
|
-
|
28
28
|
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
|
29
29
|
options[:debug] = true
|
30
30
|
}
|
@@ -47,7 +47,11 @@ module Rack
|
|
47
47
|
|
48
48
|
opts.separator ""
|
49
49
|
opts.separator "Rack options:"
|
50
|
-
opts.on("-
|
50
|
+
opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
|
51
|
+
options[:builder] = line
|
52
|
+
}
|
53
|
+
|
54
|
+
opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick)") { |s|
|
51
55
|
options[:server] = s
|
52
56
|
}
|
53
57
|
|
@@ -77,6 +81,24 @@ module Rack
|
|
77
81
|
options[:pid] = ::File.expand_path(f)
|
78
82
|
}
|
79
83
|
|
84
|
+
opts.separator ""
|
85
|
+
opts.separator "Profiling options:"
|
86
|
+
|
87
|
+
opts.on("--heap HEAPFILE", "Build the application, then dump the heap to HEAPFILE") do |e|
|
88
|
+
options[:heapfile] = e
|
89
|
+
end
|
90
|
+
|
91
|
+
opts.on("--profile PROFILE", "Dump CPU or Memory profile to PROFILE (defaults to a tempfile)") do |e|
|
92
|
+
options[:profile_file] = e
|
93
|
+
end
|
94
|
+
|
95
|
+
opts.on("--profile-mode MODE", "Profile mode (cpu|wall|object)") do |e|
|
96
|
+
{ cpu: true, wall: true, object: true }.fetch(e.to_sym) do
|
97
|
+
raise OptionParser::InvalidOption, "unknown profile mode: #{e}"
|
98
|
+
end
|
99
|
+
options[:profile_mode] = e.to_sym
|
100
|
+
end
|
101
|
+
|
80
102
|
opts.separator ""
|
81
103
|
opts.separator "Common options:"
|
82
104
|
|
@@ -114,7 +136,7 @@ module Rack
|
|
114
136
|
|
115
137
|
has_options = false
|
116
138
|
server.valid_options.each do |name, description|
|
117
|
-
next if
|
139
|
+
next if /^(Host|Port)[^a-zA-Z]/.match?(name.to_s) # ignore handler's host and port options, we do our own.
|
118
140
|
info << " -O %-21s %s" % [name, description]
|
119
141
|
has_options = true
|
120
142
|
end
|
@@ -152,7 +174,9 @@ module Rack
|
|
152
174
|
|
153
175
|
# Options may include:
|
154
176
|
# * :app
|
155
|
-
# a rack application to run (overrides :config)
|
177
|
+
# a rack application to run (overrides :config and :builder)
|
178
|
+
# * :builder
|
179
|
+
# a string to evaluate a Rack::Builder from
|
156
180
|
# * :config
|
157
181
|
# a rackup configuration file path to load (.ru)
|
158
182
|
# * :environment
|
@@ -182,6 +206,14 @@ module Rack
|
|
182
206
|
# add given paths to $LOAD_PATH
|
183
207
|
# * :require
|
184
208
|
# require the given libraries
|
209
|
+
#
|
210
|
+
# Additional options for profiling app initialization include:
|
211
|
+
# * :heapfile
|
212
|
+
# location for ObjectSpace.dump_all to write the output to
|
213
|
+
# * :profile_file
|
214
|
+
# location for CPU/Memory (StackProf) profile output (defaults to a tempfile)
|
215
|
+
# * :profile_mode
|
216
|
+
# StackProf profile mode (cpu|wall|object)
|
185
217
|
def initialize(options = nil)
|
186
218
|
@ignore_options = []
|
187
219
|
|
@@ -206,12 +238,12 @@ module Rack
|
|
206
238
|
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
207
239
|
|
208
240
|
{
|
209
|
-
:
|
210
|
-
:
|
211
|
-
:
|
212
|
-
:
|
213
|
-
:
|
214
|
-
:
|
241
|
+
environment: environment,
|
242
|
+
pid: nil,
|
243
|
+
Port: 9292,
|
244
|
+
Host: default_host,
|
245
|
+
AccessLog: [],
|
246
|
+
config: "config.ru"
|
215
247
|
}
|
216
248
|
end
|
217
249
|
|
@@ -222,12 +254,12 @@ module Rack
|
|
222
254
|
class << self
|
223
255
|
def logging_middleware
|
224
256
|
lambda { |server|
|
225
|
-
server.server.name
|
257
|
+
/CGI/.match?(server.server.name) || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
|
226
258
|
}
|
227
259
|
end
|
228
260
|
|
229
261
|
def default_middleware_by_environment
|
230
|
-
m = Hash.new {|h,k| h[k] = []}
|
262
|
+
m = Hash.new {|h, k| h[k] = []}
|
231
263
|
m["deployment"] = [
|
232
264
|
[Rack::ContentLength],
|
233
265
|
[Rack::Chunked],
|
@@ -280,7 +312,9 @@ module Rack
|
|
280
312
|
|
281
313
|
# Touch the wrapped app, so that the config.ru is loaded before
|
282
314
|
# daemonization (i.e. before chdir, etc).
|
283
|
-
|
315
|
+
handle_profiling(options[:heapfile], options[:profile_mode], options[:profile_file]) do
|
316
|
+
wrapped_app
|
317
|
+
end
|
284
318
|
|
285
319
|
daemonize_app if options[:daemonize]
|
286
320
|
|
@@ -321,6 +355,44 @@ module Rack
|
|
321
355
|
app
|
322
356
|
end
|
323
357
|
|
358
|
+
def handle_profiling(heapfile, profile_mode, filename)
|
359
|
+
if heapfile
|
360
|
+
require "objspace"
|
361
|
+
ObjectSpace.trace_object_allocations_start
|
362
|
+
yield
|
363
|
+
GC.start
|
364
|
+
::File.open(heapfile, "w") { |f| ObjectSpace.dump_all(output: f) }
|
365
|
+
exit
|
366
|
+
end
|
367
|
+
|
368
|
+
if profile_mode
|
369
|
+
require "stackprof"
|
370
|
+
require "tempfile"
|
371
|
+
|
372
|
+
make_profile_name(filename) do |filename|
|
373
|
+
::File.open(filename, "w") do |f|
|
374
|
+
StackProf.run(mode: profile_mode, out: f) do
|
375
|
+
yield
|
376
|
+
end
|
377
|
+
puts "Profile written to: #{filename}"
|
378
|
+
end
|
379
|
+
end
|
380
|
+
exit
|
381
|
+
end
|
382
|
+
|
383
|
+
yield
|
384
|
+
end
|
385
|
+
|
386
|
+
def make_profile_name(filename)
|
387
|
+
if filename
|
388
|
+
yield filename
|
389
|
+
else
|
390
|
+
::Dir::Tmpname.create("profile.dump") do |tmpname, _, _|
|
391
|
+
yield tmpname
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
324
396
|
def build_app_from_string
|
325
397
|
Rack::Builder.new_from_string(self.options[:builder])
|
326
398
|
end
|
@@ -1,3 +1,5 @@
|
|
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,15 +8,62 @@ require 'time'
|
|
6
8
|
require 'rack/request'
|
7
9
|
require 'rack/response'
|
8
10
|
require 'securerandom'
|
11
|
+
require 'digest/sha2'
|
9
12
|
|
10
13
|
module Rack
|
11
14
|
|
12
15
|
module Session
|
13
16
|
|
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
|
+
|
32
|
+
def empty?; false; end
|
33
|
+
def to_s; raise; 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
|
+
|
14
43
|
module Abstract
|
15
44
|
# SessionHash is responsible to lazily load the session from store.
|
16
45
|
|
17
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
|
+
|
59
|
+
def transform_keys(&block)
|
60
|
+
hash = dup
|
61
|
+
each do |key, value|
|
62
|
+
hash[block.call(key)] = value
|
63
|
+
end
|
64
|
+
hash
|
65
|
+
end
|
66
|
+
|
18
67
|
include Enumerable
|
19
68
|
attr_writer :id
|
20
69
|
|
@@ -57,7 +106,7 @@ module Rack
|
|
57
106
|
@data[key.to_s]
|
58
107
|
end
|
59
108
|
|
60
|
-
def fetch(key, default=Unspecified, &block)
|
109
|
+
def fetch(key, default = Unspecified, &block)
|
61
110
|
load_for_read!
|
62
111
|
if default == Unspecified
|
63
112
|
@data.fetch(key.to_s, &block)
|
@@ -160,11 +209,7 @@ module Rack
|
|
160
209
|
end
|
161
210
|
|
162
211
|
def stringify_keys(other)
|
163
|
-
|
164
|
-
other.each do |key, value|
|
165
|
-
hash[key.to_s] = value
|
166
|
-
end
|
167
|
-
hash
|
212
|
+
other.transform_keys(&:to_s)
|
168
213
|
end
|
169
214
|
end
|
170
215
|
|
@@ -199,22 +244,22 @@ module Rack
|
|
199
244
|
|
200
245
|
class Persisted
|
201
246
|
DEFAULT_OPTIONS = {
|
202
|
-
:
|
203
|
-
:
|
204
|
-
:
|
205
|
-
:
|
206
|
-
:
|
207
|
-
:
|
208
|
-
:
|
209
|
-
:
|
210
|
-
:
|
211
|
-
:
|
212
|
-
:
|
247
|
+
key: RACK_SESSION,
|
248
|
+
path: '/',
|
249
|
+
domain: nil,
|
250
|
+
expire_after: nil,
|
251
|
+
secure: false,
|
252
|
+
httponly: true,
|
253
|
+
defer: false,
|
254
|
+
renew: false,
|
255
|
+
sidbits: 128,
|
256
|
+
cookie_only: true,
|
257
|
+
secure_random: ::SecureRandom
|
213
258
|
}.freeze
|
214
259
|
|
215
260
|
attr_reader :key, :default_options, :sid_secure
|
216
261
|
|
217
|
-
def initialize(app, options={})
|
262
|
+
def initialize(app, options = {})
|
218
263
|
@app = app
|
219
264
|
@default_options = self.class::DEFAULT_OPTIONS.merge(options)
|
220
265
|
@key = @default_options.delete(:key)
|
@@ -226,7 +271,7 @@ module Rack
|
|
226
271
|
context(env)
|
227
272
|
end
|
228
273
|
|
229
|
-
def context(env, app
|
274
|
+
def context(env, app = @app)
|
230
275
|
req = make_request env
|
231
276
|
prepare_session(req)
|
232
277
|
status, headers, body = app.call(req.env)
|
@@ -349,7 +394,7 @@ module Rack
|
|
349
394
|
|
350
395
|
session.send(:load!) unless loaded_session?(session)
|
351
396
|
session_id ||= session.id
|
352
|
-
session_data = session.to_hash.delete_if { |k,v| v.nil? }
|
397
|
+
session_data = session.to_hash.delete_if { |k, v| v.nil? }
|
353
398
|
|
354
399
|
if not data = write_session(req, session_id, session_data, options)
|
355
400
|
req.get_header(RACK_ERRORS).puts("Warning! #{self.class.name} failed to save session. Content dropped.")
|
@@ -357,7 +402,7 @@ module Rack
|
|
357
402
|
req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
|
358
403
|
else
|
359
404
|
cookie = Hash.new
|
360
|
-
cookie[:value] = data
|
405
|
+
cookie[:value] = cookie_value(data)
|
361
406
|
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
362
407
|
cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
|
363
408
|
set_cookie(req, res, cookie.merge!(options))
|
@@ -365,6 +410,10 @@ module Rack
|
|
365
410
|
end
|
366
411
|
public :commit_session
|
367
412
|
|
413
|
+
def cookie_value(data)
|
414
|
+
data
|
415
|
+
end
|
416
|
+
|
368
417
|
# Sets the cookie back to the client with session id. We skip the cookie
|
369
418
|
# setting if the value didn't change (sid is the same) or expires was given.
|
370
419
|
|
@@ -406,6 +455,40 @@ module Rack
|
|
406
455
|
end
|
407
456
|
end
|
408
457
|
|
458
|
+
class PersistedSecure < Persisted
|
459
|
+
class SecureSessionHash < SessionHash
|
460
|
+
def [](key)
|
461
|
+
if key == "session_id"
|
462
|
+
load_for_read!
|
463
|
+
id.public_id
|
464
|
+
else
|
465
|
+
super
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
def generate_sid(*)
|
471
|
+
public_id = super
|
472
|
+
|
473
|
+
SessionId.new(public_id)
|
474
|
+
end
|
475
|
+
|
476
|
+
def extract_session_id(*)
|
477
|
+
public_id = super
|
478
|
+
public_id && SessionId.new(public_id)
|
479
|
+
end
|
480
|
+
|
481
|
+
private
|
482
|
+
|
483
|
+
def session_class
|
484
|
+
SecureSessionHash
|
485
|
+
end
|
486
|
+
|
487
|
+
def cookie_value(data)
|
488
|
+
data.cookie_value
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
409
492
|
class ID < Persisted
|
410
493
|
def self.inherited(klass)
|
411
494
|
k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID }
|
data/lib/rack/session/cookie.rb
CHANGED
@@ -1,9 +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'
|
6
8
|
require 'json'
|
9
|
+
require 'base64'
|
7
10
|
|
8
11
|
module Rack
|
9
12
|
|
@@ -45,15 +48,15 @@ module Rack
|
|
45
48
|
# })
|
46
49
|
#
|
47
50
|
|
48
|
-
class Cookie < Abstract::
|
51
|
+
class Cookie < Abstract::PersistedSecure
|
49
52
|
# Encode session cookies as Base64
|
50
53
|
class Base64
|
51
54
|
def encode(str)
|
52
|
-
|
55
|
+
::Base64.strict_encode64(str)
|
53
56
|
end
|
54
57
|
|
55
58
|
def decode(str)
|
56
|
-
|
59
|
+
::Base64.decode64(str)
|
57
60
|
end
|
58
61
|
|
59
62
|
# Encode session cookies as Marshaled Base64 data
|
@@ -103,7 +106,7 @@ module Rack
|
|
103
106
|
|
104
107
|
attr_reader :coder
|
105
108
|
|
106
|
-
def initialize(app, options={})
|
109
|
+
def initialize(app, options = {})
|
107
110
|
@secrets = options.values_at(:secret, :old_secret).compact
|
108
111
|
@hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1)
|
109
112
|
|
@@ -116,8 +119,8 @@ module Rack
|
|
116
119
|
|
117
120
|
Called from: #{caller[0]}.
|
118
121
|
MSG
|
119
|
-
@coder
|
120
|
-
super(app, options.merge!(:
|
122
|
+
@coder = options[:coder] ||= Base64::Marshal.new
|
123
|
+
super(app, options.merge!(cookie_only: true))
|
121
124
|
end
|
122
125
|
|
123
126
|
private
|
@@ -137,9 +140,7 @@ module Rack
|
|
137
140
|
session_data = request.cookies[@key]
|
138
141
|
|
139
142
|
if @secrets.size > 0 && session_data
|
140
|
-
|
141
|
-
digest.reverse! if digest
|
142
|
-
session_data.reverse! if session_data
|
143
|
+
session_data, _, digest = session_data.rpartition('--')
|
143
144
|
session_data = nil unless digest_match?(session_data, digest)
|
144
145
|
end
|
145
146
|
|
@@ -147,12 +148,21 @@ module Rack
|
|
147
148
|
end
|
148
149
|
end
|
149
150
|
|
150
|
-
def persistent_session_id!(data, sid=nil)
|
151
|
+
def persistent_session_id!(data, sid = nil)
|
151
152
|
data ||= {}
|
152
153
|
data["session_id"] ||= sid || generate_sid
|
153
154
|
data
|
154
155
|
end
|
155
156
|
|
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
|
+
|
156
166
|
def write_session(req, session_id, session, options)
|
157
167
|
session = session.merge("session_id" => session_id)
|
158
168
|
session_data = coder.encode(session)
|
@@ -165,7 +175,7 @@ module Rack
|
|
165
175
|
req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
|
166
176
|
nil
|
167
177
|
else
|
168
|
-
session_data
|
178
|
+
SessionId.new(session_id, session_data)
|
169
179
|
end
|
170
180
|
end
|
171
181
|
|
@@ -1,93 +1,10 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rack/session/
|
4
|
-
require 'memcache'
|
3
|
+
require 'rack/session/dalli'
|
5
4
|
|
6
5
|
module Rack
|
7
6
|
module Session
|
8
|
-
|
9
|
-
|
10
|
-
# maintained in the cookie.
|
11
|
-
# You may treat Session::Memcache as you would Session::Pool with the
|
12
|
-
# following caveats.
|
13
|
-
#
|
14
|
-
# * Setting :expire_after to 0 would note to the Memcache server to hang
|
15
|
-
# onto the session data until it would drop it according to it's own
|
16
|
-
# specifications. However, the cookie sent to the client would expire
|
17
|
-
# immediately.
|
18
|
-
#
|
19
|
-
# Note that memcache does drop data before it may be listed to expire. For
|
20
|
-
# a full description of behaviour, please see memcache's documentation.
|
21
|
-
|
22
|
-
class Memcache < Abstract::ID
|
23
|
-
attr_reader :mutex, :pool
|
24
|
-
|
25
|
-
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
|
26
|
-
:namespace => 'rack:session',
|
27
|
-
:memcache_server => 'localhost:11211'
|
28
|
-
|
29
|
-
def initialize(app, options={})
|
30
|
-
super
|
31
|
-
|
32
|
-
@mutex = Mutex.new
|
33
|
-
mserv = @default_options[:memcache_server]
|
34
|
-
mopts = @default_options.reject{|k,v| !MemCache::DEFAULT_OPTIONS.include? k }
|
35
|
-
|
36
|
-
@pool = options[:cache] || MemCache.new(mserv, mopts)
|
37
|
-
unless @pool.active? and @pool.servers.any?(&:alive?)
|
38
|
-
raise 'No memcache servers'
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def generate_sid
|
43
|
-
loop do
|
44
|
-
sid = super
|
45
|
-
break sid unless @pool.get(sid, true)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def get_session(env, sid)
|
50
|
-
with_lock(env) do
|
51
|
-
unless sid and session = @pool.get(sid)
|
52
|
-
sid, session = generate_sid, {}
|
53
|
-
unless /^STORED/ =~ @pool.add(sid, session)
|
54
|
-
raise "Session collision on '#{sid.inspect}'"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
[sid, session]
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def set_session(env, session_id, new_session, options)
|
62
|
-
expiry = options[:expire_after]
|
63
|
-
expiry = expiry.nil? ? 0 : expiry + 1
|
64
|
-
|
65
|
-
with_lock(env) do
|
66
|
-
@pool.set session_id, new_session, expiry
|
67
|
-
session_id
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def destroy_session(env, session_id, options)
|
72
|
-
with_lock(env) do
|
73
|
-
@pool.delete(session_id)
|
74
|
-
generate_sid unless options[:drop]
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def with_lock(env)
|
79
|
-
@mutex.lock if env[RACK_MULTITHREAD]
|
80
|
-
yield
|
81
|
-
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
82
|
-
if $VERBOSE
|
83
|
-
warn "#{self} is unable to find memcached server."
|
84
|
-
warn $!.inspect
|
85
|
-
end
|
86
|
-
raise
|
87
|
-
ensure
|
88
|
-
@mutex.unlock if @mutex.locked?
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
7
|
+
warn "Rack::Session::Memcache is deprecated, please use Rack::Session::Dalli from 'dalli' gem instead."
|
8
|
+
Memcache = Dalli
|
92
9
|
end
|
93
10
|
end
|