rack 0.3.0 → 0.4.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.
- data/AUTHORS +1 -0
- data/RDOX +61 -3
- data/README +94 -9
- data/Rakefile +36 -32
- data/SPEC +1 -7
- data/bin/rackup +31 -13
- data/lib/rack.rb +8 -19
- data/lib/rack/auth/digest/params.rb +2 -2
- data/lib/rack/auth/openid.rb +406 -80
- data/lib/rack/builder.rb +1 -1
- data/lib/rack/cascade.rb +10 -0
- data/lib/rack/commonlogger.rb +6 -1
- data/lib/rack/deflater.rb +63 -0
- data/lib/rack/directory.rb +158 -0
- data/lib/rack/file.rb +11 -5
- data/lib/rack/handler.rb +44 -0
- data/lib/rack/handler/evented_mongrel.rb +8 -0
- data/lib/rack/handler/fastcgi.rb +1 -0
- data/lib/rack/handler/mongrel.rb +21 -1
- data/lib/rack/lint.rb +20 -13
- data/lib/rack/mock.rb +1 -0
- data/lib/rack/request.rb +69 -2
- data/lib/rack/session/abstract/id.rb +140 -0
- data/lib/rack/session/memcache.rb +97 -0
- data/lib/rack/session/pool.rb +50 -59
- data/lib/rack/showstatus.rb +3 -1
- data/lib/rack/urlmap.rb +12 -12
- data/lib/rack/utils.rb +88 -9
- data/test/cgi/lighttpd.conf +1 -1
- data/test/cgi/test.fcgi +1 -2
- data/test/cgi/test.ru +2 -2
- data/test/spec_rack_auth_openid.rb +137 -0
- data/test/spec_rack_camping.rb +37 -33
- data/test/spec_rack_cascade.rb +15 -0
- data/test/spec_rack_cgi.rb +4 -3
- data/test/spec_rack_deflater.rb +70 -0
- data/test/spec_rack_directory.rb +56 -0
- data/test/spec_rack_fastcgi.rb +4 -3
- data/test/spec_rack_file.rb +11 -1
- data/test/spec_rack_handler.rb +24 -0
- data/test/spec_rack_lint.rb +19 -33
- data/test/spec_rack_mongrel.rb +71 -0
- data/test/spec_rack_request.rb +91 -1
- data/test/spec_rack_session_memcache.rb +132 -0
- data/test/spec_rack_session_pool.rb +48 -1
- data/test/spec_rack_showstatus.rb +5 -4
- data/test/spec_rack_urlmap.rb +60 -25
- data/test/spec_rack_utils.rb +118 -1
- data/test/testrequest.rb +3 -1
- metadata +67 -44
data/lib/rack/showstatus.rb
CHANGED
@@ -25,7 +25,9 @@ module Rack
|
|
25
25
|
req = Rack::Request.new(env)
|
26
26
|
message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
|
27
27
|
detail = env["rack.showstatus.detail"] || message
|
28
|
-
|
28
|
+
body = @template.result(binding)
|
29
|
+
size = body.respond_to?(:bytesize) ? body.bytesize : body.size
|
30
|
+
[status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]]
|
29
31
|
else
|
30
32
|
[status, headers, body]
|
31
33
|
end
|
data/lib/rack/urlmap.rb
CHANGED
@@ -20,26 +20,26 @@ module Rack
|
|
20
20
|
host = nil
|
21
21
|
end
|
22
22
|
|
23
|
-
|
23
|
+
unless location[0] == ?/
|
24
|
+
raise ArgumentError, "paths need to start with /"
|
25
|
+
end
|
26
|
+
location = location.chomp('/')
|
24
27
|
|
25
28
|
[host, location, app]
|
26
|
-
}.sort_by { |(h, l, a)| -l.size }
|
29
|
+
}.sort_by { |(h, l, a)| -l.size } # Longest path first
|
27
30
|
end
|
28
31
|
|
29
32
|
def call(env)
|
30
33
|
path = env["PATH_INFO"].to_s.squeeze("/")
|
31
34
|
hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
|
32
35
|
@mapping.each { |host, location, app|
|
33
|
-
|
34
|
-
|| (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
env["PATH_INFO"] = "/" if env["PATH_INFO"].empty?
|
41
|
-
return app.call(env)
|
42
|
-
end
|
36
|
+
next unless (hHost == host || sName == host \
|
37
|
+
|| (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
|
38
|
+
next unless location == path[0, location.size]
|
39
|
+
next unless path[location.size] == nil || path[location.size] == ?/
|
40
|
+
env["SCRIPT_NAME"] += location
|
41
|
+
env["PATH_INFO"] = path[location.size..-1]
|
42
|
+
return app.call(env)
|
43
43
|
}
|
44
44
|
[404, {"Content-Type" => "text/plain"}, ["Not Found: #{path}"]]
|
45
45
|
end
|
data/lib/rack/utils.rb
CHANGED
@@ -23,7 +23,7 @@ module Rack
|
|
23
23
|
end
|
24
24
|
module_function :unescape
|
25
25
|
|
26
|
-
# Stolen from Mongrel:
|
26
|
+
# Stolen from Mongrel, with some small modifications:
|
27
27
|
# Parses a query string by breaking it up at the '&'
|
28
28
|
# and ';' characters. You can also use this to parse
|
29
29
|
# cookies by changing the characters used in the second
|
@@ -31,8 +31,10 @@ module Rack
|
|
31
31
|
|
32
32
|
def parse_query(qs, d = '&;')
|
33
33
|
params = {}
|
34
|
-
|
35
|
-
|
34
|
+
|
35
|
+
(qs || '').split(/[#{d}] */n).each do |p|
|
36
|
+
k, v = unescape(p).split('=', 2)
|
37
|
+
|
36
38
|
if cur = params[k]
|
37
39
|
if cur.class == Array
|
38
40
|
params[k] << v
|
@@ -42,11 +44,22 @@ module Rack
|
|
42
44
|
else
|
43
45
|
params[k] = v
|
44
46
|
end
|
45
|
-
|
46
|
-
|
47
|
+
end
|
48
|
+
|
47
49
|
return params
|
48
50
|
end
|
49
51
|
module_function :parse_query
|
52
|
+
|
53
|
+
def build_query(params)
|
54
|
+
params.map { |k, v|
|
55
|
+
if v.class == Array
|
56
|
+
build_query(v.map { |x| [k, x] })
|
57
|
+
else
|
58
|
+
escape(k) + "=" + escape(v)
|
59
|
+
end
|
60
|
+
}.join("&")
|
61
|
+
end
|
62
|
+
module_function :build_query
|
50
63
|
|
51
64
|
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
52
65
|
def escape_html(string)
|
@@ -58,14 +71,77 @@ module Rack
|
|
58
71
|
end
|
59
72
|
module_function :escape_html
|
60
73
|
|
61
|
-
|
62
|
-
|
63
|
-
|
74
|
+
def select_best_encoding(available_encodings, accept_encoding)
|
75
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
76
|
+
|
77
|
+
expanded_accept_encoding =
|
78
|
+
accept_encoding.map { |m, q|
|
79
|
+
if m == "*"
|
80
|
+
(available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
|
81
|
+
else
|
82
|
+
[[m, q]]
|
83
|
+
end
|
84
|
+
}.inject([]) { |mem, list|
|
85
|
+
mem + list
|
86
|
+
}
|
87
|
+
|
88
|
+
encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
|
89
|
+
|
90
|
+
unless encoding_candidates.include?("identity")
|
91
|
+
encoding_candidates.push("identity")
|
64
92
|
end
|
93
|
+
|
94
|
+
expanded_accept_encoding.find_all { |m, q|
|
95
|
+
q == 0.0
|
96
|
+
}.each { |m, _|
|
97
|
+
encoding_candidates.delete(m)
|
98
|
+
}
|
99
|
+
|
100
|
+
return (encoding_candidates & available_encodings)[0]
|
101
|
+
end
|
102
|
+
module_function :select_best_encoding
|
103
|
+
|
104
|
+
# The recommended manner in which to implement a contexting application
|
105
|
+
# is to define a method #context in which a new Context is instantiated.
|
106
|
+
#
|
107
|
+
# As a Context is a glorified block, it is highly recommended that you
|
108
|
+
# define the contextual block within the application's operational scope.
|
109
|
+
# This would typically the application as you're place into Rack's stack.
|
110
|
+
#
|
111
|
+
# class MyObject
|
112
|
+
# ...
|
113
|
+
# def context app
|
114
|
+
# Rack::Utils::Context.new app do |env|
|
115
|
+
# do_stuff
|
116
|
+
# response = app.call(env)
|
117
|
+
# do_more_stuff
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
# ...
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# mobj = MyObject.new
|
124
|
+
# app = mobj.context other_app
|
125
|
+
# Rack::Handler::Mongrel.new app
|
126
|
+
class Context < Proc
|
65
127
|
alias_method :old_inspect, :inspect
|
128
|
+
attr_reader :for, :app
|
129
|
+
def initialize app_f, app_r
|
130
|
+
raise 'running context not provided' unless app_f
|
131
|
+
raise 'running context does not respond to #context' unless app_f.respond_to? :context
|
132
|
+
raise 'application context not provided' unless app_r
|
133
|
+
raise 'application context does not respond to #call' unless app_r.respond_to? :call
|
134
|
+
@for = app_f
|
135
|
+
@app = app_r
|
136
|
+
end
|
66
137
|
def inspect
|
67
138
|
"#{old_inspect} ==> #{@for.inspect} ==> #{@app.inspect}"
|
68
139
|
end
|
140
|
+
def context app_r
|
141
|
+
raise 'new application context not provided' unless app_r
|
142
|
+
raise 'new application context does not respond to #call' unless app_r.respond_to? :call
|
143
|
+
@for.context app_r
|
144
|
+
end
|
69
145
|
def pretty_print pp
|
70
146
|
pp.text old_inspect
|
71
147
|
pp.nest 1 do
|
@@ -187,7 +263,10 @@ module Rack
|
|
187
263
|
content_type = head[/Content-Type: (.*)\r\n/ni, 1]
|
188
264
|
name = head[/Content-Disposition:.* name="?([^\";]*)"?/ni, 1]
|
189
265
|
|
190
|
-
|
266
|
+
if filename
|
267
|
+
body = Tempfile.new("RackMultipart")
|
268
|
+
body.binmode if body.respond_to?(:binmode)
|
269
|
+
end
|
191
270
|
|
192
271
|
next
|
193
272
|
end
|
data/test/cgi/lighttpd.conf
CHANGED
data/test/cgi/test.fcgi
CHANGED
data/test/cgi/test.ru
CHANGED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
|
3
|
+
begin
|
4
|
+
# requires the ruby-openid gem
|
5
|
+
require 'rack/auth/openid'
|
6
|
+
|
7
|
+
context "Rack::Auth::OpenID" do
|
8
|
+
OID = Rack::Auth::OpenID
|
9
|
+
realm = 'http://path/arf'
|
10
|
+
ruri = %w{arf arf/blargh}
|
11
|
+
auri = ruri.map{|u|'/'+u}
|
12
|
+
furi = auri.map{|u|'http://path'+u}
|
13
|
+
|
14
|
+
specify 'realm uri should be absolute and have a path' do
|
15
|
+
lambda{OID.new('/path')}.
|
16
|
+
should.raise ArgumentError
|
17
|
+
lambda{OID.new('http://path')}.
|
18
|
+
should.raise ArgumentError
|
19
|
+
lambda{OID.new('http://path/')}.
|
20
|
+
should.not.raise
|
21
|
+
lambda{OID.new('http://path/arf')}.
|
22
|
+
should.not.raise
|
23
|
+
end
|
24
|
+
|
25
|
+
specify 'uri options should be absolute' do
|
26
|
+
[:login_good, :login_fail, :login_quit, :return_to].each do |param|
|
27
|
+
ruri.each do |uri|
|
28
|
+
lambda{OID.new(realm, {param=>uri})}.
|
29
|
+
should.raise ArgumentError
|
30
|
+
end
|
31
|
+
auri.each do |uri|
|
32
|
+
lambda{OID.new(realm, {param=>uri})}.
|
33
|
+
should.raise ArgumentError
|
34
|
+
end
|
35
|
+
furi.each do |uri|
|
36
|
+
lambda{OID.new(realm, {param=>uri})}.
|
37
|
+
should.not.raise
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
specify 'return_to should be absolute and be under the realm' do
|
43
|
+
lambda{OID.new(realm, {:return_to => 'http://path'})}.
|
44
|
+
should.raise ArgumentError
|
45
|
+
lambda{OID.new(realm, {:return_to => 'http://path/'})}.
|
46
|
+
should.raise ArgumentError
|
47
|
+
lambda{OID.new(realm, {:return_to => 'http://path/arf'})}.
|
48
|
+
should.not.raise
|
49
|
+
lambda{OID.new(realm, {:return_to => 'http://path/arf/'})}.
|
50
|
+
should.not.raise
|
51
|
+
lambda{OID.new(realm, {:return_to => 'http://path/arf/blargh'})}.
|
52
|
+
should.not.raise
|
53
|
+
end
|
54
|
+
|
55
|
+
specify 'extensions should be a module' do
|
56
|
+
ext = Object.new
|
57
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
58
|
+
should.raise(TypeError).
|
59
|
+
message.should.match(/not a module/)
|
60
|
+
ext2 = Module.new
|
61
|
+
lambda{OID.new(realm).add_extension(ext2)}.
|
62
|
+
should.raise(ArgumentError).
|
63
|
+
message.should.not.match(/not a module/)
|
64
|
+
end
|
65
|
+
|
66
|
+
specify 'extensions should have required constants defined' do
|
67
|
+
ext = Module.new
|
68
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
69
|
+
should.raise(ArgumentError).
|
70
|
+
message.should.match(/missing/)
|
71
|
+
ext::Request = nil
|
72
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
73
|
+
should.raise(ArgumentError).
|
74
|
+
message.should.match(/missing/).
|
75
|
+
should.not.match(/Request/)
|
76
|
+
ext::Response = nil
|
77
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
78
|
+
should.raise(ArgumentError).
|
79
|
+
message.should.match(/missing/).
|
80
|
+
should.not.match(/Response/)
|
81
|
+
ext::NS_URI = nil
|
82
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
83
|
+
should.raise(TypeError).
|
84
|
+
message.should.not.match(/missing/)
|
85
|
+
end
|
86
|
+
|
87
|
+
specify 'extensions should have Request and Response defined and inherit from OpenID::Extension' do
|
88
|
+
$-w, w = nil, $-w # yuck
|
89
|
+
ext = Module.new
|
90
|
+
ext::Request = nil
|
91
|
+
ext::Response = nil
|
92
|
+
ext::NS_URI = nil
|
93
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
94
|
+
should.raise(TypeError).
|
95
|
+
message.should.match(/not a class/)
|
96
|
+
ext::Request = Class.new()
|
97
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
98
|
+
should.raise(TypeError).
|
99
|
+
message.should.match(/not a class/)
|
100
|
+
ext::Response = Class.new()
|
101
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
102
|
+
should.raise(ArgumentError).
|
103
|
+
message.should.match(/not a decendant/)
|
104
|
+
ext::Request = Class.new(::OpenID::Extension)
|
105
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
106
|
+
should.raise(ArgumentError).
|
107
|
+
message.should.match(/not a decendant/)
|
108
|
+
ext::Response = Class.new(::OpenID::Extension)
|
109
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
110
|
+
should.raise(TypeError).
|
111
|
+
message.should.match(/NS_URI/)
|
112
|
+
$-w = w
|
113
|
+
end
|
114
|
+
|
115
|
+
specify 'extensions should have NS_URI defined and be a string of an absolute http uri' do
|
116
|
+
$-w, w = nil, $-w # yuck
|
117
|
+
ext = Module.new
|
118
|
+
ext::Request = Class.new(::OpenID::Extension)
|
119
|
+
ext::Response = Class.new(::OpenID::Extension)
|
120
|
+
ext::NS_URI = nil
|
121
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
122
|
+
should.raise(TypeError).
|
123
|
+
message.should.match(/not a string/)
|
124
|
+
ext::NS_URI = 'openid.net'
|
125
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
126
|
+
should.raise(ArgumentError).
|
127
|
+
message.should.match(/not an http uri/)
|
128
|
+
ext::NS_URI = 'http://openid.net'
|
129
|
+
lambda{OID.new(realm).add_extension(ext)}.
|
130
|
+
should.not.raise
|
131
|
+
$-w = w
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
rescue LoadError
|
136
|
+
$stderr.puts "Skipping Rack::Auth::OpenID tests (ruby-openid 2 is required). `gem install ruby-openid` and try again."
|
137
|
+
end
|
data/test/spec_rack_camping.rb
CHANGED
@@ -2,46 +2,50 @@ require 'test/spec'
|
|
2
2
|
require 'stringio'
|
3
3
|
require 'uri'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
require '
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
module
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
5
|
+
begin
|
6
|
+
require 'rack/mock'
|
7
|
+
|
8
|
+
$-w, w = nil, $-w # yuck
|
9
|
+
require 'camping'
|
10
|
+
require 'rack/adapter/camping'
|
11
|
+
|
12
|
+
Camping.goes :CampApp
|
13
|
+
module CampApp
|
14
|
+
module Controllers
|
15
|
+
class HW < R('/')
|
16
|
+
def get
|
17
|
+
@headers["X-Served-By"] = URI("http://rack.rubyforge.org")
|
18
|
+
"Camping works!"
|
19
|
+
end
|
20
|
+
|
21
|
+
def post
|
22
|
+
"Data: #{input.foo}"
|
23
|
+
end
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
25
|
-
|
26
|
-
$-w = w
|
27
|
+
$-w = w
|
27
28
|
|
28
|
-
context "Rack::Adapter::Camping" do
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
context "Rack::Adapter::Camping" do
|
30
|
+
specify "works with GET" do
|
31
|
+
res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)).
|
32
|
+
get("/")
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
res.should.be.ok
|
35
|
+
res["Content-Type"].should.equal "text/html"
|
36
|
+
res["X-Served-By"].should.equal "http://rack.rubyforge.org"
|
36
37
|
|
37
|
-
|
38
|
-
|
38
|
+
res.body.should.equal "Camping works!"
|
39
|
+
end
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
specify "works with POST" do
|
42
|
+
res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)).
|
43
|
+
post("/", :input => "foo=bar")
|
43
44
|
|
44
|
-
|
45
|
-
|
45
|
+
res.should.be.ok
|
46
|
+
res.body.should.equal "Data: bar"
|
47
|
+
end
|
46
48
|
end
|
49
|
+
rescue LoadError
|
50
|
+
$stderr.puts "Skipping Rack::Adapter::Camping tests (Camping is required). `gem install camping` and try again."
|
47
51
|
end
|
data/test/spec_rack_cascade.rb
CHANGED
@@ -32,4 +32,19 @@ context "Rack::Cascade" do
|
|
32
32
|
lambda { Rack::MockRequest.new(Rack::Cascade.new([])).get("/") }.
|
33
33
|
should.raise(ArgumentError)
|
34
34
|
end
|
35
|
+
|
36
|
+
specify "should append new app" do
|
37
|
+
cascade = Rack::Cascade.new([], [404, 403])
|
38
|
+
lambda { Rack::MockRequest.new(cascade).get('/cgi/test') }.
|
39
|
+
should.raise(ArgumentError)
|
40
|
+
cascade << app2
|
41
|
+
Rack::MockRequest.new(cascade).get('/cgi/test').should.be.not_found
|
42
|
+
Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.not_found
|
43
|
+
cascade << app1
|
44
|
+
Rack::MockRequest.new(cascade).get('/cgi/test').should.be.ok
|
45
|
+
Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.forbidden
|
46
|
+
Rack::MockRequest.new(cascade).get('/foo').should.be.not_found
|
47
|
+
cascade << app3
|
48
|
+
Rack::MockRequest.new(cascade).get('/foo').should.be.ok
|
49
|
+
end
|
35
50
|
end
|