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.

Files changed (50) hide show
  1. data/AUTHORS +1 -0
  2. data/RDOX +61 -3
  3. data/README +94 -9
  4. data/Rakefile +36 -32
  5. data/SPEC +1 -7
  6. data/bin/rackup +31 -13
  7. data/lib/rack.rb +8 -19
  8. data/lib/rack/auth/digest/params.rb +2 -2
  9. data/lib/rack/auth/openid.rb +406 -80
  10. data/lib/rack/builder.rb +1 -1
  11. data/lib/rack/cascade.rb +10 -0
  12. data/lib/rack/commonlogger.rb +6 -1
  13. data/lib/rack/deflater.rb +63 -0
  14. data/lib/rack/directory.rb +158 -0
  15. data/lib/rack/file.rb +11 -5
  16. data/lib/rack/handler.rb +44 -0
  17. data/lib/rack/handler/evented_mongrel.rb +8 -0
  18. data/lib/rack/handler/fastcgi.rb +1 -0
  19. data/lib/rack/handler/mongrel.rb +21 -1
  20. data/lib/rack/lint.rb +20 -13
  21. data/lib/rack/mock.rb +1 -0
  22. data/lib/rack/request.rb +69 -2
  23. data/lib/rack/session/abstract/id.rb +140 -0
  24. data/lib/rack/session/memcache.rb +97 -0
  25. data/lib/rack/session/pool.rb +50 -59
  26. data/lib/rack/showstatus.rb +3 -1
  27. data/lib/rack/urlmap.rb +12 -12
  28. data/lib/rack/utils.rb +88 -9
  29. data/test/cgi/lighttpd.conf +1 -1
  30. data/test/cgi/test.fcgi +1 -2
  31. data/test/cgi/test.ru +2 -2
  32. data/test/spec_rack_auth_openid.rb +137 -0
  33. data/test/spec_rack_camping.rb +37 -33
  34. data/test/spec_rack_cascade.rb +15 -0
  35. data/test/spec_rack_cgi.rb +4 -3
  36. data/test/spec_rack_deflater.rb +70 -0
  37. data/test/spec_rack_directory.rb +56 -0
  38. data/test/spec_rack_fastcgi.rb +4 -3
  39. data/test/spec_rack_file.rb +11 -1
  40. data/test/spec_rack_handler.rb +24 -0
  41. data/test/spec_rack_lint.rb +19 -33
  42. data/test/spec_rack_mongrel.rb +71 -0
  43. data/test/spec_rack_request.rb +91 -1
  44. data/test/spec_rack_session_memcache.rb +132 -0
  45. data/test/spec_rack_session_pool.rb +48 -1
  46. data/test/spec_rack_showstatus.rb +5 -4
  47. data/test/spec_rack_urlmap.rb +60 -25
  48. data/test/spec_rack_utils.rb +118 -1
  49. data/test/testrequest.rb +3 -1
  50. metadata +67 -44
@@ -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
- [status, headers.merge("Content-Type" => "text/html"), [@template.result(binding)]]
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
@@ -20,26 +20,26 @@ module Rack
20
20
  host = nil
21
21
  end
22
22
 
23
- location = "" if location == "/"
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 } # Longest path first
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
- if (hHost == host || sName == host \
34
- || (host.nil? && (hHost == sName || hHost == sName+':'+sPort))) \
35
- and location == path[0, location.size] \
36
- and (path[location.size] == nil || path[location.size] == ?/)
37
- env["SCRIPT_NAME"] += location.dup
38
- env["PATH_INFO"] = path[location.size..-1]
39
- env["PATH_INFO"].gsub!(/\/\z/, '')
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
@@ -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
- (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
35
- k, v=unescape(p).split('=',2)
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
- class Context < Proc
62
- def initialize app_f=nil, app_r=nil
63
- @for, @app = app_f, app_r
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
- body = Tempfile.new("RackMultipart") if filename
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
@@ -5,7 +5,7 @@ server.port = 9203
5
5
 
6
6
  server.event-handler = "freebsd-kqueue"
7
7
 
8
- cgi.assign = ("/test" => "/usr/bin/ruby",
8
+ cgi.assign = ("/test" => "",
9
9
  # ".ru" => ""
10
10
  )
11
11
 
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # -*- ruby -*-
3
3
 
4
- $: << File.join(File.dirname(__FILE__), "..", "..", "lib")
5
-
4
+ $:.unshift '../../lib'
6
5
  require 'rack'
7
6
  require '../testrequest'
8
7
 
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env ruby ../../bin/rackup
2
- #\ -E deployment -I ~/projects/rack/lib
1
+ #!/usr/bin/env ../../bin/rackup
2
+ #\ -E deployment -I ../../lib
3
3
  # -*- ruby -*-
4
4
 
5
5
  require '../testrequest'
@@ -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
@@ -2,46 +2,50 @@ require 'test/spec'
2
2
  require 'stringio'
3
3
  require 'uri'
4
4
 
5
- require 'rack/mock'
6
-
7
- $-w, w = nil, $-w # yuck
8
- require 'camping'
9
- require 'rack/adapter/camping'
10
-
11
- Camping.goes :CampApp
12
- module CampApp
13
- module Controllers
14
- class HW < R('/')
15
- def get
16
- @headers["X-Served-By"] = URI("http://rack.rubyforge.org")
17
- "Camping works!"
18
- end
19
-
20
- def post
21
- "Data: #{input.foo}"
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
- end
26
- $-w = w
27
+ $-w = w
27
28
 
28
- context "Rack::Adapter::Camping" do
29
- specify "works with GET" do
30
- res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)).
31
- get("/")
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
- res.should.be.ok
34
- res["Content-Type"].should.equal "text/html"
35
- res["X-Served-By"].should.equal "http://rack.rubyforge.org"
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
- res.body.should.equal "Camping works!"
38
- end
38
+ res.body.should.equal "Camping works!"
39
+ end
39
40
 
40
- specify "works with POST" do
41
- res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)).
42
- post("/", :input => "foo=bar")
41
+ specify "works with POST" do
42
+ res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)).
43
+ post("/", :input => "foo=bar")
43
44
 
44
- res.should.be.ok
45
- res.body.should.equal "Data: bar"
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
@@ -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