rack-rack-contrib 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. data/COPYING +18 -0
  2. data/README.rdoc +74 -0
  3. data/Rakefile +97 -0
  4. data/lib/rack/contrib.rb +32 -0
  5. data/lib/rack/contrib/accept_format.rb +44 -0
  6. data/lib/rack/contrib/backstage.rb +20 -0
  7. data/lib/rack/contrib/bounce_favicon.rb +16 -0
  8. data/lib/rack/contrib/callbacks.rb +37 -0
  9. data/lib/rack/contrib/config.rb +16 -0
  10. data/lib/rack/contrib/csshttprequest.rb +39 -0
  11. data/lib/rack/contrib/deflect.rb +137 -0
  12. data/lib/rack/contrib/etag.rb +20 -0
  13. data/lib/rack/contrib/evil.rb +12 -0
  14. data/lib/rack/contrib/garbagecollector.rb +14 -0
  15. data/lib/rack/contrib/jsonp.rb +41 -0
  16. data/lib/rack/contrib/lighttpd_script_name_fix.rb +16 -0
  17. data/lib/rack/contrib/locale.rb +31 -0
  18. data/lib/rack/contrib/mailexceptions.rb +120 -0
  19. data/lib/rack/contrib/nested_params.rb +143 -0
  20. data/lib/rack/contrib/not_found.rb +18 -0
  21. data/lib/rack/contrib/post_body_content_type_parser.rb +40 -0
  22. data/lib/rack/contrib/proctitle.rb +30 -0
  23. data/lib/rack/contrib/profiler.rb +106 -0
  24. data/lib/rack/contrib/relative_redirect.rb +44 -0
  25. data/lib/rack/contrib/response_cache.rb +59 -0
  26. data/lib/rack/contrib/route_exceptions.rb +48 -0
  27. data/lib/rack/contrib/sendfile.rb +142 -0
  28. data/lib/rack/contrib/signals.rb +63 -0
  29. data/lib/rack/contrib/time_zone.rb +25 -0
  30. data/rack-contrib.gemspec +87 -0
  31. data/test/404.html +1 -0
  32. data/test/Maintenance.html +1 -0
  33. data/test/mail_settings.rb +12 -0
  34. data/test/spec_rack_accept_format.rb +41 -0
  35. data/test/spec_rack_backstage.rb +26 -0
  36. data/test/spec_rack_callbacks.rb +65 -0
  37. data/test/spec_rack_config.rb +22 -0
  38. data/test/spec_rack_contrib.rb +8 -0
  39. data/test/spec_rack_csshttprequest.rb +66 -0
  40. data/test/spec_rack_deflect.rb +107 -0
  41. data/test/spec_rack_etag.rb +23 -0
  42. data/test/spec_rack_evil.rb +19 -0
  43. data/test/spec_rack_garbagecollector.rb +13 -0
  44. data/test/spec_rack_jsonp.rb +34 -0
  45. data/test/spec_rack_lighttpd_script_name_fix.rb +16 -0
  46. data/test/spec_rack_mailexceptions.rb +97 -0
  47. data/test/spec_rack_nested_params.rb +46 -0
  48. data/test/spec_rack_not_found.rb +17 -0
  49. data/test/spec_rack_post_body_content_type_parser.rb +32 -0
  50. data/test/spec_rack_proctitle.rb +26 -0
  51. data/test/spec_rack_profiler.rb +37 -0
  52. data/test/spec_rack_relative_redirect.rb +78 -0
  53. data/test/spec_rack_response_cache.rb +137 -0
  54. data/test/spec_rack_sendfile.rb +86 -0
  55. metadata +171 -0
@@ -0,0 +1,25 @@
1
+ module Rack
2
+ class TimeZone
3
+ Javascript = <<-EOJ
4
+ function setTimezoneCookie() {
5
+ var offset = (new Date()).getTimezoneOffset()
6
+ var date = new Date();
7
+ date.setTime(date.getTime()+3600000);
8
+ document.cookie = "utc_offset="+offset+"; expires="+date.toGMTString();+"; path=/";
9
+ }
10
+ EOJ
11
+
12
+ def initialize(app)
13
+ @app = app
14
+ end
15
+
16
+ def call(env)
17
+ request = Rack::Request.new(env)
18
+ if utc_offset = request.cookies["utc_offset"]
19
+ env["rack.timezone.utc_offset"] = -(utc_offset.to_i * 60)
20
+ end
21
+
22
+ @app.call(env)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,87 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+
5
+ s.name = 'rack-contrib'
6
+ s.version = '0.9.1'
7
+ s.date = '2009-03-07'
8
+
9
+ s.description = "Contributed Rack Middleware and Utilities"
10
+ s.summary = "Contributed Rack Middleware and Utilities"
11
+
12
+ s.authors = ["rack-devel"]
13
+ s.email = "rack-devel@googlegroups.com"
14
+
15
+ # = MANIFEST =
16
+ s.files = %w[
17
+ COPYING
18
+ README.rdoc
19
+ Rakefile
20
+ lib/rack/contrib.rb
21
+ lib/rack/contrib/accept_format.rb
22
+ lib/rack/contrib/backstage.rb
23
+ lib/rack/contrib/bounce_favicon.rb
24
+ lib/rack/contrib/callbacks.rb
25
+ lib/rack/contrib/config.rb
26
+ lib/rack/contrib/csshttprequest.rb
27
+ lib/rack/contrib/deflect.rb
28
+ lib/rack/contrib/etag.rb
29
+ lib/rack/contrib/evil.rb
30
+ lib/rack/contrib/garbagecollector.rb
31
+ lib/rack/contrib/jsonp.rb
32
+ lib/rack/contrib/lighttpd_script_name_fix.rb
33
+ lib/rack/contrib/locale.rb
34
+ lib/rack/contrib/mailexceptions.rb
35
+ lib/rack/contrib/nested_params.rb
36
+ lib/rack/contrib/not_found.rb
37
+ lib/rack/contrib/post_body_content_type_parser.rb
38
+ lib/rack/contrib/proctitle.rb
39
+ lib/rack/contrib/profiler.rb
40
+ lib/rack/contrib/relative_redirect.rb
41
+ lib/rack/contrib/response_cache.rb
42
+ lib/rack/contrib/route_exceptions.rb
43
+ lib/rack/contrib/sendfile.rb
44
+ lib/rack/contrib/signals.rb
45
+ lib/rack/contrib/time_zone.rb
46
+ rack-contrib.gemspec
47
+ test/404.html
48
+ test/Maintenance.html
49
+ test/mail_settings.rb
50
+ test/spec_rack_accept_format.rb
51
+ test/spec_rack_backstage.rb
52
+ test/spec_rack_callbacks.rb
53
+ test/spec_rack_config.rb
54
+ test/spec_rack_contrib.rb
55
+ test/spec_rack_csshttprequest.rb
56
+ test/spec_rack_deflect.rb
57
+ test/spec_rack_etag.rb
58
+ test/spec_rack_evil.rb
59
+ test/spec_rack_garbagecollector.rb
60
+ test/spec_rack_jsonp.rb
61
+ test/spec_rack_lighttpd_script_name_fix.rb
62
+ test/spec_rack_mailexceptions.rb
63
+ test/spec_rack_nested_params.rb
64
+ test/spec_rack_not_found.rb
65
+ test/spec_rack_post_body_content_type_parser.rb
66
+ test/spec_rack_proctitle.rb
67
+ test/spec_rack_profiler.rb
68
+ test/spec_rack_relative_redirect.rb
69
+ test/spec_rack_response_cache.rb
70
+ test/spec_rack_sendfile.rb
71
+ ]
72
+ # = MANIFEST =
73
+
74
+ s.test_files = s.files.select {|path| path =~ /^test\/spec_.*\.rb/}
75
+
76
+ s.extra_rdoc_files = %w[README.rdoc COPYING]
77
+ s.add_dependency 'rack', '~> 0.9.1'
78
+ s.add_dependency 'test-spec', '~> 0.9.0'
79
+ s.add_development_dependency 'tmail', '>= 1.2'
80
+ s.add_development_dependency 'json', '>= 1.1'
81
+
82
+ s.has_rdoc = true
83
+ s.homepage = "http://github.com/rack/rack-contrib/"
84
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "rack-contrib", "--main", "README"]
85
+ s.require_paths = %w[lib]
86
+ s.rubygems_version = '1.1.1'
87
+ end
data/test/404.html ADDED
@@ -0,0 +1 @@
1
+ Not Found
@@ -0,0 +1 @@
1
+ Under maintenance.
@@ -0,0 +1,12 @@
1
+ TEST_SMTP = nil
2
+
3
+ # Enable SMTP tests by providing the following for your SMTP server.
4
+ #
5
+ # TEST_SMTP = {
6
+ # :server => 'localhost',
7
+ # :domain => 'localhost',
8
+ # :port => 25,
9
+ # :authentication => :login,
10
+ # :user_name => nil,
11
+ # :password => nil
12
+ # }
@@ -0,0 +1,41 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/accept_format'
4
+ require 'rack/mime'
5
+
6
+ context "Rack::AcceptFormat" do
7
+ specify "should do nothing when a format extension is already provided" do
8
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, env['PATH_INFO']] }
9
+ request = Rack::MockRequest.env_for("/resource.json")
10
+ body = Rack::AcceptFormat.new(app).call(request).last
11
+ body.should == "/resource.json"
12
+ end
13
+
14
+ context "there is no format extension" do
15
+ Rack::Mime::MIME_TYPES.clear
16
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, env['PATH_INFO']] }
17
+ def mime(ext, type)
18
+ ext = ".#{ext}" unless ext.to_s[0] == ?.
19
+ Rack::Mime::MIME_TYPES[ext.to_s] = type
20
+ end
21
+
22
+ specify "should add the default extension if no Accept header" do
23
+ request = Rack::MockRequest.env_for("/resource")
24
+ body = Rack::AcceptFormat.new(app).call(request).last
25
+ body.should == "/resource#{Rack::AcceptFormat::DEFAULT_EXTENSION}"
26
+ end
27
+
28
+ specify "should add the default extension if the Accept header is not registered in the Mime::Types" do
29
+ request = Rack::MockRequest.env_for("/resource", 'HTTP_ACCEPT' => 'application/json;q=1.0, text/html;q=0.8, */*;q=0.1')
30
+ body = Rack::AcceptFormat.new(app).call(request).last
31
+ body.should == "/resource#{Rack::AcceptFormat::DEFAULT_EXTENSION}"
32
+ end
33
+
34
+ specify "should add the correct extension if the Accept header is registered in the Mime::Types" do
35
+ mime :json, 'application/json'
36
+ request = Rack::MockRequest.env_for("/resource", 'HTTP_ACCEPT' => 'application/json;q=1.0, text/html;q=0.8, */*;q=0.1')
37
+ body = Rack::AcceptFormat.new(app).call(request).last
38
+ body.should == "/resource.json"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ require 'test/spec'
2
+ require 'rack/builder'
3
+ require 'rack/mock'
4
+ require 'rack/contrib/backstage'
5
+
6
+ context "Rack::Backstage" do
7
+ specify "shows maintenances page if present" do
8
+ app = Rack::Builder.new do
9
+ use Rack::Backstage, 'test/Maintenance.html'
10
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
11
+ end
12
+ response = Rack::MockRequest.new(app).get('/')
13
+ response.body.should.equal('Under maintenance.')
14
+ response.status.should.equal(503)
15
+ end
16
+
17
+ specify "passes on request if page is not present" do
18
+ app = Rack::Builder.new do
19
+ use Rack::Backstage, 'test/Nonsense.html'
20
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
21
+ end
22
+ response = Rack::MockRequest.new(app).get('/')
23
+ response.body.should.equal('Hello, World!')
24
+ response.status.should.equal(200)
25
+ end
26
+ end
@@ -0,0 +1,65 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+
4
+ class Flame
5
+ def call(env)
6
+ env['flame'] = 'F Lifo..'
7
+ end
8
+ end
9
+
10
+ class Pacify
11
+ def initialize(with)
12
+ @with = with
13
+ end
14
+
15
+ def call(env)
16
+ env['peace'] = @with
17
+ end
18
+ end
19
+
20
+ class Finale
21
+ def call(response)
22
+ status, headers, body = response
23
+
24
+ headers['last'] = 'Finale'
25
+ $old_status = status
26
+
27
+ [201, headers, body]
28
+ end
29
+ end
30
+
31
+ class TheEnd
32
+ def call(response)
33
+ status, headers, body = response
34
+
35
+ headers['last'] = 'TheEnd'
36
+ [201, headers, body]
37
+ end
38
+ end
39
+
40
+ context "Rack::Callbacks" do
41
+ specify "works for love and small stack trace" do
42
+ callback_app = Rack::Callbacks.new do
43
+ before Flame
44
+ before Pacify, "with love"
45
+
46
+ run lambda {|env| [200, {}, [env['flame'], env['peace']]] }
47
+
48
+ after Finale
49
+ after TheEnd
50
+ end
51
+
52
+ app = Rack::Builder.new do
53
+ run callback_app
54
+ end.to_app
55
+
56
+ response = Rack::MockRequest.new(app).get("/")
57
+
58
+ response.body.should.equal 'F Lifo..with love'
59
+
60
+ $old_status.should.equal 200
61
+ response.status.should.equal 201
62
+
63
+ response.headers['last'].should.equal 'TheEnd'
64
+ end
65
+ end
@@ -0,0 +1,22 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/config'
4
+
5
+ context "Rack::Config" do
6
+
7
+ specify "should accept a block that modifies the environment" do
8
+ app = Rack::Builder.new do
9
+ use Rack::Lint
10
+ use Rack::ContentLength
11
+ use Rack::Config do |env|
12
+ env['greeting'] = 'hello'
13
+ end
14
+ run lambda { |env|
15
+ [200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']]
16
+ }
17
+ end
18
+ response = Rack::MockRequest.new(app).get('/')
19
+ response.body.should.equal('hello')
20
+ end
21
+
22
+ end
@@ -0,0 +1,8 @@
1
+ require 'test/spec'
2
+ require 'rack/contrib'
3
+
4
+ context "Rack::Contrib" do
5
+ specify "should expose release" do
6
+ Rack::Contrib.should.respond_to :release
7
+ end
8
+ end
@@ -0,0 +1,66 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+
4
+ begin
5
+ require 'csshttprequest'
6
+ require 'rack/contrib/csshttprequest'
7
+
8
+ context "Rack::CSSHTTPRequest" do
9
+
10
+ before(:each) do
11
+ @test_body = '{"bar":"foo"}'
12
+ @test_headers = {'Content-Type' => 'text/plain'}
13
+ @encoded_body = CSSHTTPRequest.encode(@test_body)
14
+ @app = lambda { |env| [200, @test_headers, [@test_body]] }
15
+ end
16
+
17
+ specify "env['csshttprequest.chr'] should be set to true when \
18
+ PATH_INFO ends with '.chr'" do
19
+ request = Rack::MockRequest.env_for("/blah.chr", :lint => true, :fatal => true)
20
+ Rack::CSSHTTPRequest.new(@app).call(request)
21
+ request['csshttprequest.chr'].should.equal true
22
+ end
23
+
24
+ specify "env['csshttprequest.chr'] should be set to true when \
25
+ request parameter _format == 'chr'" do
26
+ request = Rack::MockRequest.env_for("/?_format=chr", :lint => true, :fatal => true)
27
+ Rack::CSSHTTPRequest.new(@app).call(request)
28
+ request['csshttprequest.chr'].should.equal true
29
+ end
30
+
31
+ specify "should not change the headers or response when !env['csshttprequest.chr']" do
32
+ request = Rack::MockRequest.env_for("/", :lint => true, :fatal => true)
33
+ status, headers, response = Rack::CSSHTTPRequest.new(@app).call(request)
34
+ headers.should.equal @test_headers
35
+ response.join.should.equal @test_body
36
+ end
37
+
38
+ context "when env['csshttprequest.chr']" do
39
+ before(:each) do
40
+ @request = Rack::MockRequest.env_for("/",
41
+ 'csshttprequest.chr' => true, :lint => true, :fatal => true)
42
+ end
43
+
44
+ specify "should modify the content length to the correct value" do
45
+ headers = Rack::CSSHTTPRequest.new(@app).call(@request)[1]
46
+ headers['Content-Length'].should.equal @encoded_body.length.to_s
47
+ end
48
+
49
+ specify "should modify the content type to the correct value" do
50
+ headers = Rack::CSSHTTPRequest.new(@app).call(@request)[1]
51
+ headers['Content-Type'].should.equal 'text/css'
52
+ end
53
+
54
+ specify "should not modify any other headers" do
55
+ headers = Rack::CSSHTTPRequest.new(@app).call(@request)[1]
56
+ headers.should.equal @test_headers.merge({
57
+ 'Content-Type' => 'text/css',
58
+ 'Content-Length' => @encoded_body.length.to_s
59
+ })
60
+ end
61
+ end
62
+
63
+ end
64
+ rescue LoadError => boom
65
+ STDERR.puts "WARN: Skipping Rack::CSSHTTPRequest tests (nbio-csshttprequest not installed)"
66
+ end
@@ -0,0 +1,107 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/deflect'
4
+
5
+ context "Rack::Deflect" do
6
+
7
+ setup do
8
+ @app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, 'cookies'] }
9
+ @mock_addr_1 = '111.111.111.111'
10
+ @mock_addr_2 = '222.222.222.222'
11
+ @mock_addr_3 = '333.333.333.333'
12
+ end
13
+
14
+ def mock_env remote_addr, path = '/'
15
+ Rack::MockRequest.env_for path, { 'REMOTE_ADDR' => remote_addr }
16
+ end
17
+
18
+ def mock_deflect options = {}
19
+ Rack::Deflect.new @app, options
20
+ end
21
+
22
+ specify "should allow regular requests to follow through" do
23
+ app = mock_deflect
24
+ status, headers, body = app.call mock_env(@mock_addr_1)
25
+ status.should.equal 200
26
+ body.should.equal 'cookies'
27
+ end
28
+
29
+ specify "should deflect requests exceeding the request threshold" do
30
+ log = StringIO.new
31
+ app = mock_deflect :request_threshold => 5, :interval => 10, :block_duration => 10, :log => log
32
+ env = mock_env @mock_addr_1
33
+
34
+ # First 5 should be fine
35
+ 5.times do
36
+ status, headers, body = app.call env
37
+ status.should.equal 200
38
+ body.should.equal 'cookies'
39
+ end
40
+
41
+ # Remaining requests should fail for 10 seconds
42
+ 10.times do
43
+ status, headers, body = app.call env
44
+ status.should.equal 403
45
+ body.should.equal ''
46
+ end
47
+
48
+ # Log should reflect that we have blocked an address
49
+ log.string.should.match(/^deflect\(\d+\/\d+\/\d+\): blocked 111.111.111.111\n/)
50
+ end
51
+
52
+ specify "should expire blocking" do
53
+ log = StringIO.new
54
+ app = mock_deflect :request_threshold => 5, :interval => 2, :block_duration => 2, :log => log
55
+ env = mock_env @mock_addr_1
56
+
57
+ # First 5 should be fine
58
+ 5.times do
59
+ status, headers, body = app.call env
60
+ status.should.equal 200
61
+ body.should.equal 'cookies'
62
+ end
63
+
64
+ # Exceeds request threshold
65
+ status, headers, body = app.call env
66
+ status.should.equal 403
67
+ body.should.equal ''
68
+
69
+ # Allow block to expire
70
+ sleep 3
71
+
72
+ # Another 5 is fine now
73
+ 5.times do
74
+ status, headers, body = app.call env
75
+ status.should.equal 200
76
+ body.should.equal 'cookies'
77
+ end
78
+
79
+ # Log should reflect block and release
80
+ log.string.should.match(/deflect.*: blocked 111\.111\.111\.111\ndeflect.*: released 111\.111\.111\.111\n/)
81
+ end
82
+
83
+ specify "should allow whitelisting of remote addresses" do
84
+ app = mock_deflect :whitelist => [@mock_addr_1], :request_threshold => 5, :interval => 2
85
+ env = mock_env @mock_addr_1
86
+
87
+ # Whitelisted addresses are always fine
88
+ 10.times do
89
+ status, headers, body = app.call env
90
+ status.should.equal 200
91
+ body.should.equal 'cookies'
92
+ end
93
+ end
94
+
95
+ specify "should allow blacklisting of remote addresses" do
96
+ app = mock_deflect :blacklist => [@mock_addr_2]
97
+
98
+ status, headers, body = app.call mock_env(@mock_addr_1)
99
+ status.should.equal 200
100
+ body.should.equal 'cookies'
101
+
102
+ status, headers, body = app.call mock_env(@mock_addr_2)
103
+ status.should.equal 403
104
+ body.should.equal ''
105
+ end
106
+
107
+ end