rack 2.0.3 → 2.0.8
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 +5 -5
- data/README.rdoc +2 -4
- data/SPEC +8 -7
- data/lib/rack.rb +1 -1
- data/lib/rack/chunked.rb +1 -1
- data/lib/rack/etag.rb +1 -1
- data/lib/rack/lock.rb +12 -3
- data/lib/rack/method_override.rb +5 -1
- data/lib/rack/mock.rb +1 -1
- data/lib/rack/multipart/parser.rb +16 -17
- data/lib/rack/request.rb +18 -5
- data/lib/rack/session/abstract/id.rb +66 -1
- data/lib/rack/session/cookie.rb +11 -2
- data/lib/rack/session/memcache.rb +20 -14
- data/lib/rack/session/pool.rb +13 -6
- data/lib/rack/show_exceptions.rb +1 -1
- data/rack.gemspec +4 -5
- data/test/spec_lock.rb +11 -1
- data/test/spec_method_override.rb +14 -0
- data/test/spec_request.rb +15 -1
- data/test/spec_session_memcache.rb +40 -3
- data/test/spec_session_pool.rb +40 -3
- data/test/spec_show_exceptions.rb +13 -0
- data/test/spec_webrick.rb +5 -8
- metadata +47 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e9142cfb8ba777286d8118d2b094a23c1fe4698b302c99966cb80670041c67f5
|
4
|
+
data.tar.gz: 341991ef42232bfecf702d98a17e671ffbbf6a95a8ff70bcca40f7c9aa9f5e85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 012e3ac8b25a2fa3c75e7cfbed5f5f4875010ecf96ee887aa9d4ed844badc614fa7decf04bfb889983004d0c310780763e8f9328c4dfb5243388a086f05ef059
|
7
|
+
data.tar.gz: 312252b7e153667c49c11fea9bdceaf679813d6e3176c6e8599fbe4741c326abc43510c3c0a48136a36d2027393e8286f11771a5c353c68269088a6f606b8372
|
data/README.rdoc
CHANGED
@@ -228,7 +228,7 @@ You are also welcome to join the #rack channel on irc.freenode.net.
|
|
228
228
|
|
229
229
|
The Rack Core Team, consisting of
|
230
230
|
|
231
|
-
*
|
231
|
+
* Leah Neukirchen (chneukirchen[https://github.com/chneukirchen])
|
232
232
|
* James Tucker (raggi[https://github.com/raggi])
|
233
233
|
* Josh Peek (josh[https://github.com/josh])
|
234
234
|
* José Valim (josevalim[https://github.com/josevalim])
|
@@ -295,10 +295,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
295
295
|
|
296
296
|
== Links
|
297
297
|
|
298
|
-
Rack:: <
|
298
|
+
Rack:: <https://rack.github.io/>
|
299
299
|
Official Rack repositories:: <https://github.com/rack>
|
300
300
|
Rack Bug Tracking:: <https://github.com/rack/rack/issues>
|
301
301
|
rack-devel mailing list:: <https://groups.google.com/group/rack-devel>
|
302
302
|
Rack's Rubyforge project:: <http://rubyforge.org/projects/rack>
|
303
|
-
|
304
|
-
Christian Neukirchen:: <http://chneukirchen.org/>
|
data/SPEC
CHANGED
@@ -60,8 +60,8 @@ below.
|
|
60
60
|
the presence or absence of the
|
61
61
|
appropriate HTTP header in the
|
62
62
|
request. See
|
63
|
-
|
64
|
-
RFC3875 section 4.1.18
|
63
|
+
<a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
|
64
|
+
RFC3875 section 4.1.18</a> for
|
65
65
|
specific behavior.
|
66
66
|
In addition to this, the Rack environment must include these
|
67
67
|
Rack-specific variables:
|
@@ -98,12 +98,13 @@ Rack-specific variables:
|
|
98
98
|
Additional environment specifications have approved to
|
99
99
|
standardized middleware APIs. None of these are required to
|
100
100
|
be implemented by the server.
|
101
|
-
<tt>rack.session</tt>:: A hash like interface for storing
|
101
|
+
<tt>rack.session</tt>:: A hash like interface for storing
|
102
|
+
request session data.
|
102
103
|
The store must implement:
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
104
|
+
store(key, value) (aliased as []=);
|
105
|
+
fetch(key, default = nil) (aliased as []);
|
106
|
+
delete(key);
|
107
|
+
clear;
|
107
108
|
<tt>rack.logger</tt>:: A common object interface for logging messages.
|
108
109
|
The object must implement:
|
109
110
|
info(message, &block)
|
data/lib/rack.rb
CHANGED
data/lib/rack/chunked.rb
CHANGED
data/lib/rack/etag.rb
CHANGED
data/lib/rack/lock.rb
CHANGED
@@ -11,12 +11,21 @@ module Rack
|
|
11
11
|
|
12
12
|
def call(env)
|
13
13
|
@mutex.lock
|
14
|
+
@env = env
|
15
|
+
@old_rack_multithread = env[RACK_MULTITHREAD]
|
14
16
|
begin
|
15
|
-
response = @app.call(env.merge(RACK_MULTITHREAD => false))
|
16
|
-
returned = response << BodyProxy.new(response.pop) {
|
17
|
+
response = @app.call(env.merge!(RACK_MULTITHREAD => false))
|
18
|
+
returned = response << BodyProxy.new(response.pop) { unlock }
|
17
19
|
ensure
|
18
|
-
|
20
|
+
unlock unless returned
|
19
21
|
end
|
20
22
|
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def unlock
|
27
|
+
@mutex.unlock
|
28
|
+
@env[RACK_MULTITHREAD] = @old_rack_multithread
|
29
|
+
end
|
21
30
|
end
|
22
31
|
end
|
data/lib/rack/method_override.rb
CHANGED
@@ -26,7 +26,11 @@ module Rack
|
|
26
26
|
req = Request.new(env)
|
27
27
|
method = method_override_param(req) ||
|
28
28
|
env[HTTP_METHOD_OVERRIDE_HEADER]
|
29
|
-
|
29
|
+
begin
|
30
|
+
method.to_s.upcase
|
31
|
+
rescue ArgumentError
|
32
|
+
env[RACK_ERRORS].puts "Invalid string for method"
|
33
|
+
end
|
30
34
|
end
|
31
35
|
|
32
36
|
private
|
data/lib/rack/mock.rb
CHANGED
@@ -39,8 +39,6 @@ module Rack
|
|
39
39
|
str
|
40
40
|
end
|
41
41
|
|
42
|
-
def eof?; @content_length == @cursor; end
|
43
|
-
|
44
42
|
def rewind
|
45
43
|
@io.rewind
|
46
44
|
end
|
@@ -65,11 +63,11 @@ module Rack
|
|
65
63
|
io = BoundedIO.new(io, content_length) if content_length
|
66
64
|
|
67
65
|
parser = new(boundary, tmpfile, bufsize, qp)
|
68
|
-
parser.on_read io.read(bufsize)
|
66
|
+
parser.on_read io.read(bufsize)
|
69
67
|
|
70
68
|
loop do
|
71
69
|
break if parser.state == :DONE
|
72
|
-
parser.on_read io.read(bufsize)
|
70
|
+
parser.on_read io.read(bufsize)
|
73
71
|
end
|
74
72
|
|
75
73
|
io.rewind
|
@@ -135,7 +133,7 @@ module Rack
|
|
135
133
|
klass = TempfilePart
|
136
134
|
@open_files += 1
|
137
135
|
else
|
138
|
-
body =
|
136
|
+
body = String.new
|
139
137
|
klass = BufferPart
|
140
138
|
end
|
141
139
|
|
@@ -165,15 +163,15 @@ module Rack
|
|
165
163
|
attr_reader :state
|
166
164
|
|
167
165
|
def initialize(boundary, tempfile, bufsize, query_parser)
|
168
|
-
@buf =
|
166
|
+
@buf = String.new
|
169
167
|
|
170
168
|
@query_parser = query_parser
|
171
169
|
@params = query_parser.make_params
|
172
170
|
@boundary = "--#{boundary}"
|
173
|
-
@boundary_size = @boundary.bytesize + EOL.size
|
174
171
|
@bufsize = bufsize
|
175
172
|
|
176
173
|
@rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
|
174
|
+
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
177
175
|
@full_boundary = @boundary
|
178
176
|
@end_boundary = @boundary + '--'
|
179
177
|
@state = :FAST_FORWARD
|
@@ -181,8 +179,8 @@ module Rack
|
|
181
179
|
@collector = Collector.new tempfile
|
182
180
|
end
|
183
181
|
|
184
|
-
def on_read content
|
185
|
-
handle_empty_content!(content
|
182
|
+
def on_read content
|
183
|
+
handle_empty_content!(content)
|
186
184
|
@buf << content
|
187
185
|
run_parser
|
188
186
|
end
|
@@ -263,15 +261,17 @@ module Rack
|
|
263
261
|
end
|
264
262
|
|
265
263
|
def handle_mime_body
|
266
|
-
if @buf
|
264
|
+
if i = @buf.index(rx)
|
267
265
|
# Save the rest.
|
268
|
-
|
269
|
-
|
270
|
-
@buf.slice!(0, 2) # Remove \r\n after the content
|
271
|
-
end
|
266
|
+
@collector.on_mime_body @mime_index, @buf.slice!(0, i)
|
267
|
+
@buf.slice!(0, 2) # Remove \r\n after the content
|
272
268
|
@state = :CONSUME_TOKEN
|
273
269
|
@mime_index += 1
|
274
270
|
else
|
271
|
+
# Save the read body part.
|
272
|
+
if @rx_max_size < @buf.size
|
273
|
+
@collector.on_mime_body @mime_index, @buf.slice!(0, @buf.size - @rx_max_size)
|
274
|
+
end
|
275
275
|
:want_read
|
276
276
|
end
|
277
277
|
end
|
@@ -356,10 +356,9 @@ module Rack
|
|
356
356
|
end
|
357
357
|
|
358
358
|
|
359
|
-
def handle_empty_content!(content
|
359
|
+
def handle_empty_content!(content)
|
360
360
|
if content.nil? || content.empty?
|
361
|
-
raise EOFError
|
362
|
-
return true
|
361
|
+
raise EOFError
|
363
362
|
end
|
364
363
|
end
|
365
364
|
end
|
data/lib/rack/request.rb
CHANGED
@@ -11,6 +11,8 @@ module Rack
|
|
11
11
|
# req.params["data"]
|
12
12
|
|
13
13
|
class Request
|
14
|
+
SCHEME_WHITELIST = %w(https http).freeze
|
15
|
+
|
14
16
|
def initialize(env)
|
15
17
|
@params = nil
|
16
18
|
super(env)
|
@@ -188,10 +190,8 @@ module Rack
|
|
188
190
|
'https'
|
189
191
|
elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
|
190
192
|
'https'
|
191
|
-
elsif
|
192
|
-
|
193
|
-
elsif get_header(HTTP_X_FORWARDED_PROTO)
|
194
|
-
get_header(HTTP_X_FORWARDED_PROTO).split(',')[0]
|
193
|
+
elsif forwarded_scheme
|
194
|
+
forwarded_scheme
|
195
195
|
else
|
196
196
|
get_header(RACK_URL_SCHEME)
|
197
197
|
end
|
@@ -261,7 +261,7 @@ module Rack
|
|
261
261
|
|
262
262
|
forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
|
263
263
|
|
264
|
-
return reject_trusted_ip_addresses(forwarded_ips).last || get_header("REMOTE_ADDR")
|
264
|
+
return reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
|
265
265
|
end
|
266
266
|
|
267
267
|
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
@@ -479,6 +479,19 @@ module Rack
|
|
479
479
|
def reject_trusted_ip_addresses(ip_addresses)
|
480
480
|
ip_addresses.reject { |ip| trusted_proxy?(ip) }
|
481
481
|
end
|
482
|
+
|
483
|
+
def forwarded_scheme
|
484
|
+
scheme_headers = [
|
485
|
+
get_header(HTTP_X_FORWARDED_SCHEME),
|
486
|
+
get_header(HTTP_X_FORWARDED_PROTO).to_s.split(',')[0]
|
487
|
+
]
|
488
|
+
|
489
|
+
scheme_headers.each do |header|
|
490
|
+
return header if SCHEME_WHITELIST.include?(header)
|
491
|
+
end
|
492
|
+
|
493
|
+
nil
|
494
|
+
end
|
482
495
|
end
|
483
496
|
|
484
497
|
include Env
|
@@ -6,11 +6,38 @@ require 'time'
|
|
6
6
|
require 'rack/request'
|
7
7
|
require 'rack/response'
|
8
8
|
require 'securerandom'
|
9
|
+
require 'digest/sha2'
|
9
10
|
|
10
11
|
module Rack
|
11
12
|
|
12
13
|
module Session
|
13
14
|
|
15
|
+
class SessionId
|
16
|
+
ID_VERSION = 2
|
17
|
+
|
18
|
+
attr_reader :public_id
|
19
|
+
|
20
|
+
def initialize(public_id)
|
21
|
+
@public_id = public_id
|
22
|
+
end
|
23
|
+
|
24
|
+
def private_id
|
25
|
+
"#{ID_VERSION}::#{hash_sid(public_id)}"
|
26
|
+
end
|
27
|
+
|
28
|
+
alias :cookie_value :public_id
|
29
|
+
|
30
|
+
def empty?; false; end
|
31
|
+
def to_s; raise; end
|
32
|
+
def inspect; public_id.inspect; end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def hash_sid(sid)
|
37
|
+
Digest::SHA256.hexdigest(sid)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
14
41
|
module Abstract
|
15
42
|
# SessionHash is responsible to lazily load the session from store.
|
16
43
|
|
@@ -357,7 +384,7 @@ module Rack
|
|
357
384
|
req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
|
358
385
|
else
|
359
386
|
cookie = Hash.new
|
360
|
-
cookie[:value] = data
|
387
|
+
cookie[:value] = cookie_value(data)
|
361
388
|
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
362
389
|
cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
|
363
390
|
set_cookie(req, res, cookie.merge!(options))
|
@@ -365,6 +392,10 @@ module Rack
|
|
365
392
|
end
|
366
393
|
public :commit_session
|
367
394
|
|
395
|
+
def cookie_value(data)
|
396
|
+
data
|
397
|
+
end
|
398
|
+
|
368
399
|
# Sets the cookie back to the client with session id. We skip the cookie
|
369
400
|
# setting if the value didn't change (sid is the same) or expires was given.
|
370
401
|
|
@@ -406,6 +437,40 @@ module Rack
|
|
406
437
|
end
|
407
438
|
end
|
408
439
|
|
440
|
+
class PersistedSecure < Persisted
|
441
|
+
class SecureSessionHash < SessionHash
|
442
|
+
def [](key)
|
443
|
+
if key == "session_id"
|
444
|
+
load_for_read!
|
445
|
+
id.public_id
|
446
|
+
else
|
447
|
+
super
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
def generate_sid(*)
|
453
|
+
public_id = super
|
454
|
+
|
455
|
+
SessionId.new(public_id)
|
456
|
+
end
|
457
|
+
|
458
|
+
def extract_session_id(*)
|
459
|
+
public_id = super
|
460
|
+
public_id && SessionId.new(public_id)
|
461
|
+
end
|
462
|
+
|
463
|
+
private
|
464
|
+
|
465
|
+
def session_class
|
466
|
+
SecureSessionHash
|
467
|
+
end
|
468
|
+
|
469
|
+
def cookie_value(data)
|
470
|
+
data.cookie_value
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
409
474
|
class ID < Persisted
|
410
475
|
def self.inherited(klass)
|
411
476
|
k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID }
|
data/lib/rack/session/cookie.rb
CHANGED
@@ -45,7 +45,7 @@ module Rack
|
|
45
45
|
# })
|
46
46
|
#
|
47
47
|
|
48
|
-
class Cookie < Abstract::
|
48
|
+
class Cookie < Abstract::PersistedSecure
|
49
49
|
# Encode session cookies as Base64
|
50
50
|
class Base64
|
51
51
|
def encode(str)
|
@@ -153,6 +153,15 @@ module Rack
|
|
153
153
|
data
|
154
154
|
end
|
155
155
|
|
156
|
+
class SessionId < DelegateClass(Session::SessionId)
|
157
|
+
attr_reader :cookie_value
|
158
|
+
|
159
|
+
def initialize(session_id, cookie_value)
|
160
|
+
super(session_id)
|
161
|
+
@cookie_value = cookie_value
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
156
165
|
def write_session(req, session_id, session, options)
|
157
166
|
session = session.merge("session_id" => session_id)
|
158
167
|
session_data = coder.encode(session)
|
@@ -165,7 +174,7 @@ module Rack
|
|
165
174
|
req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
|
166
175
|
nil
|
167
176
|
else
|
168
|
-
session_data
|
177
|
+
SessionId.new(session_id, session_data)
|
169
178
|
end
|
170
179
|
end
|
171
180
|
|
@@ -19,7 +19,7 @@ module Rack
|
|
19
19
|
# Note that memcache does drop data before it may be listed to expire. For
|
20
20
|
# a full description of behaviour, please see memcache's documentation.
|
21
21
|
|
22
|
-
class Memcache < Abstract::
|
22
|
+
class Memcache < Abstract::PersistedSecure
|
23
23
|
attr_reader :mutex, :pool
|
24
24
|
|
25
25
|
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
|
@@ -42,15 +42,15 @@ module Rack
|
|
42
42
|
def generate_sid
|
43
43
|
loop do
|
44
44
|
sid = super
|
45
|
-
break sid unless @pool.get(sid, true)
|
45
|
+
break sid unless @pool.get(sid.private_id, true)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
def
|
50
|
-
with_lock(
|
51
|
-
unless sid and session =
|
49
|
+
def find_session(req, sid)
|
50
|
+
with_lock(req) do
|
51
|
+
unless sid and session = get_session_with_fallback(sid)
|
52
52
|
sid, session = generate_sid, {}
|
53
|
-
unless /^STORED/ =~ @pool.add(sid, session)
|
53
|
+
unless /^STORED/ =~ @pool.add(sid.private_id, session)
|
54
54
|
raise "Session collision on '#{sid.inspect}'"
|
55
55
|
end
|
56
56
|
end
|
@@ -58,25 +58,26 @@ module Rack
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
def
|
61
|
+
def write_session(req, session_id, new_session, options)
|
62
62
|
expiry = options[:expire_after]
|
63
63
|
expiry = expiry.nil? ? 0 : expiry + 1
|
64
64
|
|
65
|
-
with_lock(
|
66
|
-
@pool.set session_id, new_session, expiry
|
65
|
+
with_lock(req) do
|
66
|
+
@pool.set session_id.private_id, new_session, expiry
|
67
67
|
session_id
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
def
|
72
|
-
with_lock(
|
73
|
-
@pool.delete(session_id)
|
71
|
+
def delete_session(req, session_id, options)
|
72
|
+
with_lock(req) do
|
73
|
+
@pool.delete(session_id.public_id)
|
74
|
+
@pool.delete(session_id.private_id)
|
74
75
|
generate_sid unless options[:drop]
|
75
76
|
end
|
76
77
|
end
|
77
78
|
|
78
|
-
def with_lock(
|
79
|
-
@mutex.lock if
|
79
|
+
def with_lock(req)
|
80
|
+
@mutex.lock if req.multithread?
|
80
81
|
yield
|
81
82
|
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
82
83
|
if $VERBOSE
|
@@ -88,6 +89,11 @@ module Rack
|
|
88
89
|
@mutex.unlock if @mutex.locked?
|
89
90
|
end
|
90
91
|
|
92
|
+
private
|
93
|
+
|
94
|
+
def get_session_with_fallback(sid)
|
95
|
+
@pool.get(sid.private_id) || @pool.get(sid.public_id)
|
96
|
+
end
|
91
97
|
end
|
92
98
|
end
|
93
99
|
end
|
data/lib/rack/session/pool.rb
CHANGED
@@ -24,7 +24,7 @@ module Rack
|
|
24
24
|
# )
|
25
25
|
# Rack::Handler::WEBrick.run sessioned
|
26
26
|
|
27
|
-
class Pool < Abstract::
|
27
|
+
class Pool < Abstract::PersistedSecure
|
28
28
|
attr_reader :mutex, :pool
|
29
29
|
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false
|
30
30
|
|
@@ -37,15 +37,15 @@ module Rack
|
|
37
37
|
def generate_sid
|
38
38
|
loop do
|
39
39
|
sid = super
|
40
|
-
break sid unless @pool.key? sid
|
40
|
+
break sid unless @pool.key? sid.private_id
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
44
|
def find_session(req, sid)
|
45
45
|
with_lock(req) do
|
46
|
-
unless sid and session =
|
46
|
+
unless sid and session = get_session_with_fallback(sid)
|
47
47
|
sid, session = generate_sid, {}
|
48
|
-
@pool.store sid, session
|
48
|
+
@pool.store sid.private_id, session
|
49
49
|
end
|
50
50
|
[sid, session]
|
51
51
|
end
|
@@ -53,14 +53,15 @@ module Rack
|
|
53
53
|
|
54
54
|
def write_session(req, session_id, new_session, options)
|
55
55
|
with_lock(req) do
|
56
|
-
@pool.store session_id, new_session
|
56
|
+
@pool.store session_id.private_id, new_session
|
57
57
|
session_id
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
61
|
def delete_session(req, session_id, options)
|
62
62
|
with_lock(req) do
|
63
|
-
@pool.delete(session_id)
|
63
|
+
@pool.delete(session_id.public_id)
|
64
|
+
@pool.delete(session_id.private_id)
|
64
65
|
generate_sid unless options[:drop]
|
65
66
|
end
|
66
67
|
end
|
@@ -71,6 +72,12 @@ module Rack
|
|
71
72
|
ensure
|
72
73
|
@mutex.unlock if @mutex.locked?
|
73
74
|
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def get_session_with_fallback(sid)
|
79
|
+
@pool[sid.private_id] || @pool[sid.public_id]
|
80
|
+
end
|
74
81
|
end
|
75
82
|
end
|
76
83
|
end
|
data/lib/rack/show_exceptions.rb
CHANGED
data/rack.gemspec
CHANGED
@@ -12,7 +12,7 @@ the simplest way possible, it unifies and distills the API for web
|
|
12
12
|
servers, web frameworks, and software in between (the so-called
|
13
13
|
middleware) into a single method call.
|
14
14
|
|
15
|
-
Also see
|
15
|
+
Also see https://rack.github.io/.
|
16
16
|
EOF
|
17
17
|
|
18
18
|
s.files = Dir['{bin/*,contrib/*,example/*,lib/**/*,test/**/*}'] +
|
@@ -23,13 +23,12 @@ EOF
|
|
23
23
|
s.extra_rdoc_files = ['README.rdoc', 'HISTORY.md']
|
24
24
|
s.test_files = Dir['test/spec_*.rb']
|
25
25
|
|
26
|
-
s.author = '
|
27
|
-
s.email = '
|
28
|
-
s.homepage = '
|
26
|
+
s.author = 'Leah Neukirchen'
|
27
|
+
s.email = 'leah@vuxu.org'
|
28
|
+
s.homepage = 'https://rack.github.io/'
|
29
29
|
s.required_ruby_version = '>= 2.2.2'
|
30
30
|
|
31
31
|
s.add_development_dependency 'minitest', "~> 5.0"
|
32
32
|
s.add_development_dependency 'minitest-sprint'
|
33
|
-
s.add_development_dependency 'concurrent-ruby'
|
34
33
|
s.add_development_dependency 'rake'
|
35
34
|
end
|
data/test/spec_lock.rb
CHANGED
@@ -147,7 +147,8 @@ describe Rack::Lock do
|
|
147
147
|
}, false)
|
148
148
|
env = Rack::MockRequest.env_for("/")
|
149
149
|
env['rack.multithread'].must_equal true
|
150
|
-
app.call(env)
|
150
|
+
_, _, body = app.call(env)
|
151
|
+
body.close
|
151
152
|
env['rack.multithread'].must_equal true
|
152
153
|
end
|
153
154
|
|
@@ -191,4 +192,13 @@ describe Rack::Lock do
|
|
191
192
|
lambda { app.call(env) }.must_raise Exception
|
192
193
|
lock.synchronized.must_equal false
|
193
194
|
end
|
195
|
+
|
196
|
+
it "not replace the environment" do
|
197
|
+
env = Rack::MockRequest.env_for("/")
|
198
|
+
app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, [inner_env.object_id.to_s]] })
|
199
|
+
|
200
|
+
_, _, body = app.call(env)
|
201
|
+
|
202
|
+
body.to_enum.to_a.must_equal [env.object_id.to_s]
|
203
|
+
end
|
194
204
|
end
|
@@ -17,6 +17,20 @@ describe Rack::MethodOverride do
|
|
17
17
|
env["REQUEST_METHOD"].must_equal "GET"
|
18
18
|
end
|
19
19
|
|
20
|
+
it "sets rack.errors for invalid UTF8 _method values" do
|
21
|
+
errors = StringIO.new
|
22
|
+
env = Rack::MockRequest.env_for("/",
|
23
|
+
:method => "POST",
|
24
|
+
:input => "_method=\xBF".b,
|
25
|
+
Rack::RACK_ERRORS => errors)
|
26
|
+
|
27
|
+
app.call env
|
28
|
+
|
29
|
+
errors.rewind
|
30
|
+
errors.read.must_equal "Invalid string for method\n"
|
31
|
+
env["REQUEST_METHOD"].must_equal "POST"
|
32
|
+
end
|
33
|
+
|
20
34
|
it "modify REQUEST_METHOD for POST requests when _method parameter is set" do
|
21
35
|
env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=put")
|
22
36
|
app.call env
|
data/test/spec_request.rb
CHANGED
@@ -572,6 +572,11 @@ class RackRequestTest < Minitest::Spec
|
|
572
572
|
request.must_be :ssl?
|
573
573
|
end
|
574
574
|
|
575
|
+
it "prevents scheme abuse" do
|
576
|
+
request = make_request(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'a."><script>alert(1)</script>'))
|
577
|
+
request.scheme.must_equal 'http'
|
578
|
+
end
|
579
|
+
|
575
580
|
it "parse cookies" do
|
576
581
|
req = make_request \
|
577
582
|
Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m")
|
@@ -1281,7 +1286,16 @@ EOF
|
|
1281
1286
|
res.body.must_equal '2.2.2.3'
|
1282
1287
|
end
|
1283
1288
|
|
1284
|
-
it "
|
1289
|
+
it "preserves ip for trusted proxy chain" do
|
1290
|
+
mock = Rack::MockRequest.new(Rack::Lint.new(ip_app))
|
1291
|
+
res = mock.get '/',
|
1292
|
+
'HTTP_X_FORWARDED_FOR' => '192.168.0.11, 192.168.0.7',
|
1293
|
+
'HTTP_CLIENT_IP' => '127.0.0.1'
|
1294
|
+
res.body.must_equal '192.168.0.11'
|
1295
|
+
|
1296
|
+
end
|
1297
|
+
|
1298
|
+
it "regards local addresses as proxies" do
|
1285
1299
|
req = make_request(Rack::MockRequest.env_for("/"))
|
1286
1300
|
req.trusted_proxy?('127.0.0.1').must_equal 0
|
1287
1301
|
req.trusted_proxy?('10.0.0.1').must_equal 0
|
@@ -226,15 +226,52 @@ begin
|
|
226
226
|
req = Rack::MockRequest.new(pool)
|
227
227
|
|
228
228
|
res0 = req.get("/")
|
229
|
-
session_id = (cookie = res0["Set-Cookie"])[session_match, 1]
|
230
|
-
ses0 = pool.pool.get(session_id, true)
|
229
|
+
session_id = Rack::Session::SessionId.new (cookie = res0["Set-Cookie"])[session_match, 1]
|
230
|
+
ses0 = pool.pool.get(session_id.private_id, true)
|
231
231
|
|
232
232
|
req.get("/", "HTTP_COOKIE" => cookie)
|
233
|
-
ses1 = pool.pool.get(session_id, true)
|
233
|
+
ses1 = pool.pool.get(session_id.private_id, true)
|
234
234
|
|
235
235
|
ses1.wont_equal ses0
|
236
236
|
end
|
237
237
|
|
238
|
+
it "can read the session with the legacy id" do
|
239
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
240
|
+
req = Rack::MockRequest.new(pool)
|
241
|
+
|
242
|
+
res0 = req.get("/")
|
243
|
+
cookie = res0["Set-Cookie"]
|
244
|
+
session_id = Rack::Session::SessionId.new cookie[session_match, 1]
|
245
|
+
ses0 = pool.pool.get(session_id.private_id, true)
|
246
|
+
pool.pool.set(session_id.public_id, ses0, 0, true)
|
247
|
+
pool.pool.delete(session_id.private_id)
|
248
|
+
|
249
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
250
|
+
res1["Set-Cookie"].must_be_nil
|
251
|
+
res1.body.must_equal '{"counter"=>2}'
|
252
|
+
pool.pool.get(session_id.private_id, true).wont_be_nil
|
253
|
+
end
|
254
|
+
|
255
|
+
it "drops the session in the legacy id as well" do
|
256
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
257
|
+
req = Rack::MockRequest.new(pool)
|
258
|
+
drop = Rack::Utils::Context.new(pool, drop_session)
|
259
|
+
dreq = Rack::MockRequest.new(drop)
|
260
|
+
|
261
|
+
res0 = req.get("/")
|
262
|
+
cookie = res0["Set-Cookie"]
|
263
|
+
session_id = Rack::Session::SessionId.new cookie[session_match, 1]
|
264
|
+
ses0 = pool.pool.get(session_id.private_id, true)
|
265
|
+
pool.pool.set(session_id.public_id, ses0, 0, true)
|
266
|
+
pool.pool.delete(session_id.private_id)
|
267
|
+
|
268
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
|
269
|
+
res2["Set-Cookie"].must_be_nil
|
270
|
+
res2.body.must_equal '{"counter"=>2}'
|
271
|
+
pool.pool.get(session_id.private_id, true).must_be_nil
|
272
|
+
pool.pool.get(session_id.public_id, true).must_be_nil
|
273
|
+
end
|
274
|
+
|
238
275
|
# anyone know how to do this better?
|
239
276
|
it "cleanly merges sessions when multithreaded" do
|
240
277
|
skip unless $DEBUG
|
data/test/spec_session_pool.rb
CHANGED
@@ -6,7 +6,7 @@ require 'rack/session/pool'
|
|
6
6
|
|
7
7
|
describe Rack::Session::Pool do
|
8
8
|
session_key = Rack::Session::Pool::DEFAULT_OPTIONS[:key]
|
9
|
-
session_match = /#{session_key}=[0-9a-fA-F]
|
9
|
+
session_match = /#{session_key}=([0-9a-fA-F]+);/
|
10
10
|
|
11
11
|
incrementor = lambda do |env|
|
12
12
|
env["rack.session"]["counter"] ||= 0
|
@@ -14,7 +14,7 @@ describe Rack::Session::Pool do
|
|
14
14
|
Rack::Response.new(env["rack.session"].inspect).to_a
|
15
15
|
end
|
16
16
|
|
17
|
-
|
17
|
+
get_session_id = Rack::Lint.new(lambda do |env|
|
18
18
|
Rack::Response.new(env["rack.session"].inspect).to_a
|
19
19
|
end)
|
20
20
|
|
@@ -143,6 +143,43 @@ describe Rack::Session::Pool do
|
|
143
143
|
pool.pool.size.must_equal 1
|
144
144
|
end
|
145
145
|
|
146
|
+
it "can read the session with the legacy id" do
|
147
|
+
pool = Rack::Session::Pool.new(incrementor)
|
148
|
+
req = Rack::MockRequest.new(pool)
|
149
|
+
|
150
|
+
res0 = req.get("/")
|
151
|
+
cookie = res0["Set-Cookie"]
|
152
|
+
session_id = Rack::Session::SessionId.new cookie[session_match, 1]
|
153
|
+
ses0 = pool.pool[session_id.private_id]
|
154
|
+
pool.pool[session_id.public_id] = ses0
|
155
|
+
pool.pool.delete(session_id.private_id)
|
156
|
+
|
157
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
158
|
+
res1["Set-Cookie"].must_be_nil
|
159
|
+
res1.body.must_equal '{"counter"=>2}'
|
160
|
+
pool.pool[session_id.private_id].wont_be_nil
|
161
|
+
end
|
162
|
+
|
163
|
+
it "drops the session in the legacy id as well" do
|
164
|
+
pool = Rack::Session::Pool.new(incrementor)
|
165
|
+
req = Rack::MockRequest.new(pool)
|
166
|
+
drop = Rack::Utils::Context.new(pool, drop_session)
|
167
|
+
dreq = Rack::MockRequest.new(drop)
|
168
|
+
|
169
|
+
res0 = req.get("/")
|
170
|
+
cookie = res0["Set-Cookie"]
|
171
|
+
session_id = Rack::Session::SessionId.new cookie[session_match, 1]
|
172
|
+
ses0 = pool.pool[session_id.private_id]
|
173
|
+
pool.pool[session_id.public_id] = ses0
|
174
|
+
pool.pool.delete(session_id.private_id)
|
175
|
+
|
176
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
|
177
|
+
res2["Set-Cookie"].must_be_nil
|
178
|
+
res2.body.must_equal '{"counter"=>2}'
|
179
|
+
pool.pool[session_id.private_id].must_be_nil
|
180
|
+
pool.pool[session_id.public_id].must_be_nil
|
181
|
+
end
|
182
|
+
|
146
183
|
# anyone know how to do this better?
|
147
184
|
it "should merge sessions when multithreaded" do
|
148
185
|
unless $DEBUG
|
@@ -191,7 +228,7 @@ describe Rack::Session::Pool do
|
|
191
228
|
end
|
192
229
|
|
193
230
|
it "does not return a cookie if cookie was not written (only read)" do
|
194
|
-
app = Rack::Session::Pool.new(
|
231
|
+
app = Rack::Session::Pool.new(get_session_id)
|
195
232
|
res = Rack::MockRequest.new(app).get("/")
|
196
233
|
res["Set-Cookie"].must_be_nil
|
197
234
|
end
|
@@ -77,4 +77,17 @@ describe Rack::ShowExceptions do
|
|
77
77
|
assert_match(res, /ShowExceptions/)
|
78
78
|
assert_match(res, /unknown location/)
|
79
79
|
end
|
80
|
+
|
81
|
+
it "knows to prefer plaintext for non-html" do
|
82
|
+
# We don't need an app for this
|
83
|
+
exc = Rack::ShowExceptions.new(nil)
|
84
|
+
|
85
|
+
[
|
86
|
+
[{ "HTTP_ACCEPT" => "text/plain" }, true],
|
87
|
+
[{ "HTTP_ACCEPT" => "text/foo" }, true],
|
88
|
+
[{ "HTTP_ACCEPT" => "text/html" }, false]
|
89
|
+
].each do |env, expected|
|
90
|
+
assert_equal(expected, exc.prefers_plaintext?(env))
|
91
|
+
end
|
92
|
+
end
|
80
93
|
end
|
data/test/spec_webrick.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
2
|
require 'rack/mock'
|
3
|
-
require '
|
4
|
-
require 'concurrent/atomic/count_down_latch'
|
3
|
+
require 'thread'
|
5
4
|
require File.expand_path('../testrequest', __FILE__)
|
6
5
|
|
7
6
|
Thread.abort_on_exception = true
|
@@ -120,8 +119,7 @@ describe Rack::Handler::WEBrick do
|
|
120
119
|
end
|
121
120
|
|
122
121
|
it "provide a .run" do
|
123
|
-
|
124
|
-
latch = Concurrent::CountDownLatch.new 1
|
122
|
+
queue = Queue.new
|
125
123
|
|
126
124
|
t = Thread.new do
|
127
125
|
Rack::Handler::WEBrick.run(lambda {},
|
@@ -132,13 +130,12 @@ describe Rack::Handler::WEBrick do
|
|
132
130
|
:AccessLog => []}) { |server|
|
133
131
|
block_ran = true
|
134
132
|
assert_kind_of WEBrick::HTTPServer, server
|
135
|
-
|
136
|
-
latch.count_down
|
133
|
+
queue.push(server)
|
137
134
|
}
|
138
135
|
end
|
139
136
|
|
140
|
-
|
141
|
-
|
137
|
+
server = queue.pop
|
138
|
+
server.shutdown
|
142
139
|
t.join
|
143
140
|
end
|
144
141
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Leah Neukirchen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: concurrent-ruby
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: rake
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -73,8 +59,8 @@ description: |
|
|
73
59
|
servers, web frameworks, and software in between (the so-called
|
74
60
|
middleware) into a single method call.
|
75
61
|
|
76
|
-
Also see
|
77
|
-
email:
|
62
|
+
Also see https://rack.github.io/.
|
63
|
+
email: leah@vuxu.org
|
78
64
|
executables:
|
79
65
|
- rackup
|
80
66
|
extensions: []
|
@@ -269,7 +255,7 @@ files:
|
|
269
255
|
- test/testrequest.rb
|
270
256
|
- test/unregistered_handler/rack/handler/unregistered.rb
|
271
257
|
- test/unregistered_handler/rack/handler/unregistered_long_one.rb
|
272
|
-
homepage:
|
258
|
+
homepage: https://rack.github.io/
|
273
259
|
licenses:
|
274
260
|
- MIT
|
275
261
|
metadata: {}
|
@@ -288,60 +274,59 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
288
274
|
- !ruby/object:Gem::Version
|
289
275
|
version: '0'
|
290
276
|
requirements: []
|
291
|
-
|
292
|
-
rubygems_version: 2.6.8
|
277
|
+
rubygems_version: 3.0.3
|
293
278
|
signing_key:
|
294
279
|
specification_version: 4
|
295
280
|
summary: a modular Ruby webserver interface
|
296
281
|
test_files:
|
297
|
-
- test/
|
298
|
-
- test/spec_auth_digest.rb
|
299
|
-
- test/spec_body_proxy.rb
|
300
|
-
- test/spec_builder.rb
|
301
|
-
- test/spec_cascade.rb
|
302
|
-
- test/spec_cgi.rb
|
303
|
-
- test/spec_chunked.rb
|
304
|
-
- test/spec_common_logger.rb
|
305
|
-
- test/spec_conditional_get.rb
|
306
|
-
- test/spec_config.rb
|
307
|
-
- test/spec_content_length.rb
|
308
|
-
- test/spec_content_type.rb
|
282
|
+
- test/spec_multipart.rb
|
309
283
|
- test/spec_deflater.rb
|
310
|
-
- test/
|
284
|
+
- test/spec_static.rb
|
285
|
+
- test/spec_session_cookie.rb
|
286
|
+
- test/spec_session_pool.rb
|
311
287
|
- test/spec_etag.rb
|
312
|
-
- test/
|
313
|
-
- test/spec_fastcgi.rb
|
314
|
-
- test/spec_file.rb
|
288
|
+
- test/spec_version.rb
|
315
289
|
- test/spec_handler.rb
|
316
|
-
- test/
|
317
|
-
- test/
|
318
|
-
- test/spec_lobster.rb
|
319
|
-
- test/spec_lock.rb
|
320
|
-
- test/spec_logger.rb
|
321
|
-
- test/spec_media_type.rb
|
322
|
-
- test/spec_method_override.rb
|
290
|
+
- test/spec_thin.rb
|
291
|
+
- test/spec_session_abstract_id.rb
|
323
292
|
- test/spec_mime.rb
|
324
|
-
- test/spec_mock.rb
|
325
|
-
- test/spec_multipart.rb
|
326
|
-
- test/spec_null_logger.rb
|
327
293
|
- test/spec_recursive.rb
|
294
|
+
- test/spec_null_logger.rb
|
295
|
+
- test/spec_media_type.rb
|
296
|
+
- test/spec_cgi.rb
|
297
|
+
- test/spec_method_override.rb
|
298
|
+
- test/spec_content_type.rb
|
299
|
+
- test/spec_session_abstract_session_hash.rb
|
328
300
|
- test/spec_request.rb
|
329
|
-
- test/
|
330
|
-
- test/
|
301
|
+
- test/spec_chunked.rb
|
302
|
+
- test/spec_show_exceptions.rb
|
331
303
|
- test/spec_runtime.rb
|
304
|
+
- test/spec_fastcgi.rb
|
305
|
+
- test/spec_common_logger.rb
|
306
|
+
- test/spec_builder.rb
|
307
|
+
- test/spec_config.rb
|
308
|
+
- test/spec_utils.rb
|
332
309
|
- test/spec_sendfile.rb
|
310
|
+
- test/spec_lobster.rb
|
311
|
+
- test/spec_lint.rb
|
312
|
+
- test/spec_conditional_get.rb
|
313
|
+
- test/spec_tempfile_reaper.rb
|
314
|
+
- test/spec_mock.rb
|
333
315
|
- test/spec_server.rb
|
334
|
-
- test/
|
335
|
-
- test/
|
336
|
-
- test/
|
337
|
-
- test/
|
338
|
-
- test/spec_session_pool.rb
|
339
|
-
- test/spec_show_exceptions.rb
|
316
|
+
- test/spec_directory.rb
|
317
|
+
- test/spec_webrick.rb
|
318
|
+
- test/spec_response.rb
|
319
|
+
- test/spec_file.rb
|
340
320
|
- test/spec_show_status.rb
|
341
|
-
- test/
|
342
|
-
- test/
|
343
|
-
- test/
|
321
|
+
- test/spec_body_proxy.rb
|
322
|
+
- test/spec_logger.rb
|
323
|
+
- test/spec_auth_digest.rb
|
344
324
|
- test/spec_urlmap.rb
|
345
|
-
- test/
|
346
|
-
- test/
|
347
|
-
- test/
|
325
|
+
- test/spec_events.rb
|
326
|
+
- test/spec_cascade.rb
|
327
|
+
- test/spec_auth_basic.rb
|
328
|
+
- test/spec_head.rb
|
329
|
+
- test/spec_lock.rb
|
330
|
+
- test/spec_rewindable_input.rb
|
331
|
+
- test/spec_session_memcache.rb
|
332
|
+
- test/spec_content_length.rb
|