rack 0.1.0 → 0.2.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.

Files changed (48) hide show
  1. data/AUTHORS +2 -0
  2. data/RDOX +46 -1
  3. data/README +19 -4
  4. data/Rakefile +9 -8
  5. data/bin/rackup +2 -0
  6. data/example/protectedlobster.rb +14 -0
  7. data/lib/rack.rb +19 -0
  8. data/lib/rack/adapter/camping.rb +6 -0
  9. data/lib/rack/auth/abstract/handler.rb +28 -0
  10. data/lib/rack/auth/abstract/request.rb +39 -0
  11. data/lib/rack/auth/basic.rb +58 -0
  12. data/lib/rack/auth/digest/md5.rb +124 -0
  13. data/lib/rack/auth/digest/nonce.rb +52 -0
  14. data/lib/rack/auth/digest/params.rb +55 -0
  15. data/lib/rack/auth/digest/request.rb +40 -0
  16. data/lib/rack/commonlogger.rb +1 -1
  17. data/lib/rack/file.rb +1 -1
  18. data/lib/rack/handler/cgi.rb +7 -7
  19. data/lib/rack/handler/fastcgi.rb +1 -1
  20. data/lib/rack/handler/mongrel.rb +8 -7
  21. data/lib/rack/handler/webrick.rb +7 -6
  22. data/lib/rack/lint.rb +3 -3
  23. data/lib/rack/lobster.rb +4 -4
  24. data/lib/rack/mock.rb +9 -33
  25. data/lib/rack/recursive.rb +1 -1
  26. data/lib/rack/reloader.rb +1 -1
  27. data/lib/rack/request.rb +32 -9
  28. data/lib/rack/response.rb +54 -8
  29. data/lib/rack/session/cookie.rb +73 -0
  30. data/lib/rack/showexceptions.rb +2 -2
  31. data/lib/rack/showstatus.rb +103 -0
  32. data/lib/rack/static.rb +38 -0
  33. data/lib/rack/urlmap.rb +1 -1
  34. data/lib/rack/utils.rb +45 -3
  35. data/test/spec_rack_auth_basic.rb +68 -0
  36. data/test/spec_rack_auth_digest.rb +167 -0
  37. data/test/spec_rack_camping.rb +3 -0
  38. data/test/spec_rack_mock.rb +2 -0
  39. data/test/spec_rack_mongrel.rb +12 -0
  40. data/test/spec_rack_request.rb +60 -0
  41. data/test/spec_rack_response.rb +50 -1
  42. data/test/spec_rack_session_cookie.rb +49 -0
  43. data/test/spec_rack_showstatus.rb +71 -0
  44. data/test/spec_rack_static.rb +37 -0
  45. data/test/spec_rack_urlmap.rb +1 -7
  46. data/test/spec_rack_webrick.rb +17 -0
  47. metadata +23 -3
  48. data/lib/rack/adapter/rails.rb +0 -65
@@ -0,0 +1,68 @@
1
+ require 'test/spec'
2
+ require 'base64'
3
+ require 'rack'
4
+
5
+ context 'Rack::Auth::Basic' do
6
+
7
+ def realm
8
+ 'WallysWorld'
9
+ end
10
+
11
+ def unprotected_app
12
+ lambda { |env| [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ] }
13
+ end
14
+
15
+ def protected_app
16
+ app = Rack::Auth::Basic.new(unprotected_app) { |username, password| 'Boss' == username }
17
+ app.realm = realm
18
+ app
19
+ end
20
+
21
+ setup do
22
+ @request = Rack::MockRequest.new(protected_app)
23
+ end
24
+
25
+ def request_with_basic_auth(username, password, &block)
26
+ request 'HTTP_AUTHORIZATION' => 'Basic ' + Base64.encode64("#{username}:#{password}"), &block
27
+ end
28
+
29
+ def request(headers = {})
30
+ yield @request.get('/', headers)
31
+ end
32
+
33
+ def assert_basic_auth_challenge(response)
34
+ response.should.be.a.client_error
35
+ response.status.should.equal 401
36
+ response.should.include 'WWW-Authenticate'
37
+ response.headers['WWW-Authenticate'].should =~ /Basic realm="/
38
+ response.body.should.be.empty
39
+ end
40
+
41
+ specify 'should challenge correctly when no credentials are specified' do
42
+ request do |response|
43
+ assert_basic_auth_challenge response
44
+ end
45
+ end
46
+
47
+ specify 'should rechallenge if incorrect credentials are specified' do
48
+ request_with_basic_auth 'joe', 'password' do |response|
49
+ assert_basic_auth_challenge response
50
+ end
51
+ end
52
+
53
+ specify 'should return application output if correct credentials are specified' do
54
+ request_with_basic_auth 'Boss', 'password' do |response|
55
+ response.status.should.equal 200
56
+ response.body.to_s.should.equal 'Hi Boss'
57
+ end
58
+ end
59
+
60
+ specify 'should return 400 Bad Request if different auth scheme used' do
61
+ request 'HTTP_AUTHORIZATION' => 'Digest params' do |response|
62
+ response.should.be.a.client_error
63
+ response.status.should.equal 400
64
+ response.should.not.include 'WWW-Authenticate'
65
+ end
66
+ end
67
+
68
+ end
@@ -0,0 +1,167 @@
1
+ require 'test/spec'
2
+ require 'rack'
3
+
4
+ context 'Rack::Auth::Digest::MD5' do
5
+
6
+ def realm
7
+ 'WallysWorld'
8
+ end
9
+
10
+ def unprotected_app
11
+ lambda do |env|
12
+ [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ]
13
+ end
14
+ end
15
+
16
+ def protected_app
17
+ app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username|
18
+ { 'Alice' => 'correct-password' }[username]
19
+ end
20
+ app.realm = realm
21
+ app.opaque = 'this-should-be-secret'
22
+ app
23
+ end
24
+
25
+ def protected_app_with_hashed_passwords
26
+ app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username|
27
+ username == 'Alice' ? Digest::MD5.hexdigest("Alice:#{realm}:correct-password") : nil
28
+ end
29
+ app.realm = realm
30
+ app.opaque = 'this-should-be-secret'
31
+ app.passwords_hashed = true
32
+ app
33
+ end
34
+
35
+ setup do
36
+ @request = Rack::MockRequest.new(protected_app)
37
+ end
38
+
39
+ def request(path, headers = {}, &block)
40
+ response = @request.get(path, headers)
41
+ block.call(response) if block
42
+ return response
43
+ end
44
+
45
+ class MockDigestRequest
46
+ def initialize(params)
47
+ @params = params
48
+ end
49
+ def method_missing(sym)
50
+ if @params.has_key? k = sym.to_s
51
+ return @params[k]
52
+ end
53
+ super
54
+ end
55
+ def method
56
+ 'GET'
57
+ end
58
+ def response(password)
59
+ Rack::Auth::Digest::MD5.new(nil).send :digest, self, password
60
+ end
61
+ end
62
+
63
+ def request_with_digest_auth(path, username, password, options = {}, &block)
64
+ response = request('/')
65
+
66
+ return response unless response.status == 401
67
+
68
+ if wait = options.delete(:wait)
69
+ sleep wait
70
+ end
71
+
72
+ challenge = response['WWW-Authenticate'].split(' ', 2).last
73
+
74
+ params = Rack::Auth::Digest::Params.parse(challenge)
75
+
76
+ params['username'] = username
77
+ params['nc'] = '00000001'
78
+ params['cnonce'] = 'nonsensenonce'
79
+ params['uri'] = path
80
+
81
+ params.update options
82
+
83
+ params['response'] = MockDigestRequest.new(params).response(password)
84
+
85
+ request(path, { 'HTTP_AUTHORIZATION' => "Digest #{params}" }, &block)
86
+ end
87
+
88
+ def assert_digest_auth_challenge(response)
89
+ response.should.be.a.client_error
90
+ response.status.should.equal 401
91
+ response.should.include 'WWW-Authenticate'
92
+ response.headers['WWW-Authenticate'].should =~ /^Digest /
93
+ response.body.should.be.empty
94
+ end
95
+
96
+ def assert_bad_request(response)
97
+ response.should.be.a.client_error
98
+ response.status.should.equal 400
99
+ response.should.not.include 'WWW-Authenticate'
100
+ end
101
+
102
+ specify 'should challenge when no credentials are specified' do
103
+ request '/' do |response|
104
+ assert_digest_auth_challenge response
105
+ end
106
+ end
107
+
108
+ specify 'should return application output if correct credentials given' do
109
+ request_with_digest_auth '/', 'Alice', 'correct-password' do |response|
110
+ response.status.should.equal 200
111
+ response.body.to_s.should.equal 'Hi Alice'
112
+ end
113
+ end
114
+
115
+ specify 'should return application output if correct credentials given (hashed passwords)' do
116
+ @request = Rack::MockRequest.new(protected_app_with_hashed_passwords)
117
+
118
+ request_with_digest_auth '/', 'Alice', 'correct-password' do |response|
119
+ response.status.should.equal 200
120
+ response.body.to_s.should.equal 'Hi Alice'
121
+ end
122
+ end
123
+
124
+ specify 'should rechallenge if incorrect username given' do
125
+ request_with_digest_auth '/', 'Bob', 'correct-password' do |response|
126
+ assert_digest_auth_challenge response
127
+ end
128
+ end
129
+
130
+ specify 'should rechallenge if incorrect password given' do
131
+ request_with_digest_auth '/', 'Alice', 'wrong-password' do |response|
132
+ assert_digest_auth_challenge response
133
+ end
134
+ end
135
+
136
+ specify 'should rechallenge with stale parameter if nonce is stale' do
137
+ begin
138
+ Rack::Auth::Digest::Nonce.time_limit = 1
139
+
140
+ request_with_digest_auth '/', 'Alice', 'correct-password', :wait => 2 do |response|
141
+ assert_digest_auth_challenge response
142
+ response.headers['WWW-Authenticate'].should =~ /\bstale=true\b/
143
+ end
144
+ ensure
145
+ Rack::Auth::Digest::Nonce.time_limit = nil
146
+ end
147
+ end
148
+
149
+ specify 'should return 400 Bad Request if incorrect qop given' do
150
+ request_with_digest_auth '/', 'Alice', 'correct-password', 'qop' => 'auth-int' do |response|
151
+ assert_bad_request response
152
+ end
153
+ end
154
+
155
+ specify 'should return 400 Bad Request if incorrect uri given' do
156
+ request_with_digest_auth '/', 'Alice', 'correct-password', 'uri' => '/foo' do |response|
157
+ assert_bad_request response
158
+ end
159
+ end
160
+
161
+ specify 'should return 400 Bad Request if different auth scheme used' do
162
+ request '/', 'HTTP_AUTHORIZATION' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==' do |response|
163
+ assert_bad_request response
164
+ end
165
+ end
166
+
167
+ end
@@ -1,5 +1,6 @@
1
1
  require 'test/spec'
2
2
  require 'stringio'
3
+ require 'uri'
3
4
 
4
5
  require 'rack/mock'
5
6
 
@@ -12,6 +13,7 @@ module CampApp
12
13
  module Controllers
13
14
  class HW < R('/')
14
15
  def get
16
+ @headers["X-Served-By"] = URI("http://rack.rubyforge.org")
15
17
  "Camping works!"
16
18
  end
17
19
 
@@ -30,6 +32,7 @@ context "Rack::Adapter::Camping" do
30
32
 
31
33
  res.should.be.ok
32
34
  res["Content-Type"].should.equal "text/html"
35
+ res["X-Served-By"].should.equal "http://rack.rubyforge.org"
33
36
 
34
37
  res.body.should.equal "Camping works!"
35
38
  end
@@ -126,6 +126,7 @@ context "Rack::MockResponse" do
126
126
  res["Content-Type"].should.equal "text/yaml"
127
127
  res.content_type.should.equal "text/yaml"
128
128
  res.content_length.should.be.nil
129
+ res.location.should.be.nil
129
130
  end
130
131
 
131
132
  specify "should provide access to the HTTP body" do
@@ -133,6 +134,7 @@ context "Rack::MockResponse" do
133
134
  res.body.should =~ /rack/
134
135
  res.should =~ /rack/
135
136
  res.should.match(/rack/)
137
+ res.should.satisfy { |r| r.match(/rack/) }
136
138
  end
137
139
 
138
140
  specify "should provide access to the Rack errors" do
@@ -81,6 +81,18 @@ context "Rack::Handler::Mongrel" do
81
81
  response["rack.url_scheme"].should.equal "http"
82
82
  end
83
83
 
84
+ specify "should provide a .run" do
85
+ block_ran = false
86
+ Thread.new {
87
+ Rack::Handler::Mongrel.run(lambda {}, {:Port => 9211}) { |server|
88
+ server.should.be.kind_of Mongrel::HttpServer
89
+ block_ran = true
90
+ }
91
+ }
92
+ sleep 1
93
+ block_ran.should.be true
94
+ end
95
+
84
96
  teardown do
85
97
  @acc.raise Mongrel::StopServer
86
98
  end
@@ -52,6 +52,48 @@ context "Rack::Request" do
52
52
  req.params.should.equal "foo" => "bar", "quux" => "bla"
53
53
  end
54
54
 
55
+ specify "can get value by key from params with #[]" do
56
+ req = Rack::Request.new \
57
+ Rack::MockRequest.env_for("?foo=quux")
58
+ req['foo'].should.equal 'quux'
59
+ req[:foo].should.equal 'quux'
60
+ end
61
+
62
+ specify "can set value to key on params with #[]=" do
63
+ req = Rack::Request.new \
64
+ Rack::MockRequest.env_for("?foo=duh")
65
+ req['foo'].should.equal 'duh'
66
+ req[:foo].should.equal 'duh'
67
+ req.params.should.equal 'foo' => 'duh'
68
+
69
+ req['foo'] = 'bar'
70
+ req.params.should.equal 'foo' => 'bar'
71
+ req['foo'].should.equal 'bar'
72
+ req[:foo].should.equal 'bar'
73
+
74
+ req[:foo] = 'jaz'
75
+ req.params.should.equal 'foo' => 'jaz'
76
+ req['foo'].should.equal 'jaz'
77
+ req[:foo].should.equal 'jaz'
78
+ end
79
+
80
+ specify "values_at answers values by keys in order given" do
81
+ req = Rack::Request.new \
82
+ Rack::MockRequest.env_for("?foo=baz&wun=der&bar=ful")
83
+ req.values_at('foo').should.equal ['baz']
84
+ req.values_at('foo', 'wun').should.equal ['baz', 'der']
85
+ req.values_at('bar', 'foo', 'wun').should.equal ['ful', 'baz', 'der']
86
+ end
87
+
88
+ specify "referrer should be extracted correct" do
89
+ req = Rack::Request.new \
90
+ Rack::MockRequest.env_for("/", "HTTP_REFERER" => "/some/path")
91
+ req.referer.should.equal "/some/path"
92
+
93
+ req = Rack::Request.new \
94
+ Rack::MockRequest.env_for("/")
95
+ req.referer.should.equal "/"
96
+ end
55
97
 
56
98
  specify "can cache, but invalidates the cache" do
57
99
  req = Rack::Request.new \
@@ -123,6 +165,24 @@ context "Rack::Request" do
123
165
  should.equal "https://example.com:8080/foo?foo"
124
166
  end
125
167
 
168
+ specify "can restore the full path" do
169
+ Rack::Request.new(Rack::MockRequest.env_for("")).fullpath.
170
+ should.equal "/"
171
+ Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).fullpath.
172
+ should.equal "/foo/"
173
+ Rack::Request.new(Rack::MockRequest.env_for("/foo")).fullpath.
174
+ should.equal "/foo"
175
+ Rack::Request.new(Rack::MockRequest.env_for("?foo")).fullpath.
176
+ should.equal "/?foo"
177
+ Rack::Request.new(Rack::MockRequest.env_for("http://example.org:8080/")).fullpath.
178
+ should.equal "/"
179
+ Rack::Request.new(Rack::MockRequest.env_for("https://example.org/")).fullpath.
180
+ should.equal "/"
181
+
182
+ Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).fullpath.
183
+ should.equal "/foo?foo"
184
+ end
185
+
126
186
  specify "can parse multipart form data" do
127
187
  # Adapted from RFC 1867.
128
188
  input = <<EOF
@@ -105,6 +105,55 @@ context "Rack::Response" do
105
105
  lambda {
106
106
  Rack::Response.new(Object.new)
107
107
  }.should.raise(TypeError).
108
- message.should =~ /String or iterable required/
108
+ message.should =~ /stringable or iterable required/
109
109
  end
110
+
111
+ specify "knows if it's empty" do
112
+ r = Rack::Response.new
113
+ r.should.be.empty
114
+ r.write "foo"
115
+ r.should.not.be.empty
116
+
117
+ r = Rack::Response.new
118
+ r.should.be.empty
119
+ r.finish
120
+ r.should.be.empty
121
+
122
+ r = Rack::Response.new
123
+ r.should.be.empty
124
+ r.finish { }
125
+ r.should.not.be.empty
126
+ end
127
+
128
+ specify "should provide access to the HTTP status" do
129
+ res = Rack::Response.new
130
+ res.status = 200
131
+ res.should.be.successful
132
+ res.should.be.ok
133
+
134
+ res.status = 404
135
+ res.should.not.be.successful
136
+ res.should.be.client_error
137
+ res.should.be.not_found
138
+
139
+ res.status = 501
140
+ res.should.not.be.successful
141
+ res.should.be.server_error
142
+
143
+ res.status = 307
144
+ res.should.be.redirect
145
+ end
146
+
147
+ specify "should provide access to the HTTP headers" do
148
+ res = Rack::Response.new
149
+ res["Content-Type"] = "text/yaml"
150
+
151
+ res.should.include "Content-Type"
152
+ res.headers["Content-Type"].should.equal "text/yaml"
153
+ res["Content-Type"].should.equal "text/yaml"
154
+ res.content_type.should.equal "text/yaml"
155
+ res.content_length.should.be.nil
156
+ res.location.should.be.nil
157
+ end
158
+
110
159
  end
@@ -0,0 +1,49 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/session/cookie'
4
+ require 'rack/mock'
5
+ require 'rack/response'
6
+
7
+ context "Rack::Session::Cookie" do
8
+ incrementor = lambda { |env|
9
+ env["rack.session"]["counter"] ||= 0
10
+ env["rack.session"]["counter"] += 1
11
+ Rack::Response.new(env["rack.session"].inspect).to_a
12
+ }
13
+
14
+ specify "creates a new cookie" do
15
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
16
+ res["Set-Cookie"].should.match("rack.session=")
17
+ res.body.should.equal '{"counter"=>1}'
18
+ end
19
+
20
+ specify "loads from a cookie" do
21
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
22
+ cookie = res["Set-Cookie"]
23
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
24
+ get("/", "HTTP_COOKIE" => cookie)
25
+ res.body.should.equal '{"counter"=>2}'
26
+ cookie = res["Set-Cookie"]
27
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
28
+ get("/", "HTTP_COOKIE" => cookie)
29
+ res.body.should.equal '{"counter"=>3}'
30
+ end
31
+
32
+ specify "survives broken cookies" do
33
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
34
+ get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
35
+ res.body.should.equal '{"counter"=>1}'
36
+ end
37
+
38
+ bigcookie = lambda { |env|
39
+ env["rack.session"]["cookie"] = "big" * 3000
40
+ Rack::Response.new(env["rack.session"].inspect).to_a
41
+ }
42
+
43
+ specify "barks on too big cookies" do
44
+ lambda {
45
+ Rack::MockRequest.new(Rack::Session::Cookie.new(bigcookie)).
46
+ get("/", :fatal => true)
47
+ }.should.raise(Rack::MockRequest::FatalWarning)
48
+ end
49
+ end